Push Ingest API

ℹ️

This feature requires an Enterprise or higher plan to use.

This feature is in Public Preview. See here for more information on what this means.

What is the ETL Push Ingest API?

The ETL Push Ingest API lets you push pipeline metadata and run events directly to Monte Carlo using the pycarlo SDK or direct HTTP. Use it to integrate any ETL or orchestration tool that Monte Carlo doesn't natively support.

Two endpoints cover everything:

EndpointWhat it ingestspycarlo method
POST /ingest/v1/etl/metadataJob/pipeline structure β€” names, tasks, schedules, lineagesvc.send_etl_metadata(...)
POST /ingest/v1/etl/runsRun execution data β€” status, timing, errors, task-level breakdownsvc.send_etl_runs(...)

Every accepted request returns an invocation_id. Save it β€” it is your primary handle for tracing a push through the downstream systems.

ℹ️

This is the push approach β€” you control when and how data is sent. If you prefer a turnkey setup where Monte Carlo handles scheduling and pagination, see Custom ETL Connectors instead.


End-to-end workflow

  1. Set up your integration β€” Create a Custom ETL integration in the Monte Carlo UI with Push selected for metadata.
  2. Create an Integration key β€” Generate a dedicated ingestion key for authenticating push requests.
  3. Push data β€” Send pipeline metadata and run events using the pycarlo SDK or direct HTTP.
  4. View pipelines β€” Once data is in Monte Carlo, pipelines appear in your workspace with status, run history, and lineage.

1. Set up your integration

Go to Settings β†’ Integrations β†’ Add β†’ Orchestration β†’ Custom ETL integration and create a connection for your ETL tool. Select Push for metadata β€” the UI will provide the ingestion URLs and resource UUID.


Save the resource UUID β€” you'll need it for every push request. You can also retrieve it later via GraphQL:

query {
  getUser {
    account {
      etlContainers{
        uuid
        name
        type
      }
    }
  }
}

2. Create an Integration key

Push requests use a dedicated integration key with scope Ingestion. A standard Monte Carlo API key will not work.

Option A β€” UI (recommended)

After creating your push-based ETL integration you can generate an ingestion key from the UI.


Option B β€” GraphQL mutation

Use the API Explorer with a GraphQL API key. Using the ETL container UUID from step 1, generate an integration key with:

mutation {
  createIntegrationKey(
    description: "ETL push ingestion key"
    scope: Ingestion
    warehouseIds: ["<etl_container_uuid>"]
  ) {
    key { id secret }
  }
}
❗️

Store the key secret immediately

The key secret is shown only once at creation time. Save it to a secure secrets manager before closing the terminal.


3. Push data

Set up your environment variables for the examples below:

export MC_INGEST_KEY_ID="<ingestion-key-id>"
export MC_INGEST_KEY_TOKEN="<ingestion-key-secret>"
export MC_RESOURCE_UUID="<resource-uuid>"
❗️

Keep credentials out of source control

Replace placeholder values with your actual credentials. Never commit real key secrets to version-controlled files or CI configuration.

Option A β€” pycarlo SDK (recommended)

Install the SDK:

pip install pycarlo

Initialize the client

import os
from pycarlo.core import Client, Session
from pycarlo.features.ingestion import IngestionService

service = IngestionService(mc_client=Client(session=Session(
    mcd_id=os.environ["MC_INGEST_KEY_ID"],
    mcd_token=os.environ["MC_INGEST_KEY_TOKEN"],
    scope="Ingestion",
)))

Push pipeline metadata

πŸ“˜

Illustrative example

The job names, task names, and lineage references below are illustrative. Replace them with values from your ETL tool.

from pycarlo.features.ingestion import (
    EtlAsset, EtlGroup, EtlTask, Schedule, Owner, AssetRef, Tag,
)

job = EtlAsset(
    # Required
    job_source_id="pipeline-123",           # stable vendor ID
    name="Build revenue model",             # display name

    # Optional β€” structure
    group=EtlGroup(
        source_id="analytics-project",      # required within group
        name="Analytics Project",
        group_type="project",
    ),
    tasks=[
        EtlTask(
            task_source_id="extract-charges",
            name="Extract charges",
            task_type="SqlOperator",
            inputs=[AssetRef(asset_type="TABLE", role="INPUT",
                             fully_qualified_name="raw.billing.charges")],
            outputs=[AssetRef(asset_type="TABLE", role="OUTPUT",
                              fully_qualified_name="staging.billing.charges_clean")],
            upstream_task_source_ids=[],
        ),
        EtlTask(
            task_source_id="build-model",
            name="Build model",
            task_type="SqlOperator",
            inputs=[AssetRef(asset_type="TABLE", role="INPUT",
                             fully_qualified_name="staging.billing.charges_clean")],
            outputs=[AssetRef(asset_type="TABLE", role="OUTPUT",
                              fully_qualified_name="prod.analytics.revenue")],
            upstream_task_source_ids=["extract-charges"],
        ),
    ],

    # Optional β€” scheduling, ownership, metadata
    description="Daily revenue model build",
    folder="/analytics/revenue",
    job_url="https://airflow.example.com/dags/build_revenue",
    is_paused=False,
    schedule=Schedule(kind="cron", cron_expression="0 2 * * *", timezone="UTC"),
    owner=Owner(primary_email="[email protected]", team="Analytics"),
    properties=[Tag(key="env", value="production")],

    # Optional β€” job-level lineage
    inputs=[
        AssetRef(asset_type="TABLE", role="INPUT",
                 fully_qualified_name="raw.billing.charges"),
    ],
    outputs=[
        AssetRef(asset_type="TABLE", role="OUTPUT",
                 fully_qualified_name="prod.analytics.revenue"),
    ],
)

result = service.send_etl_metadata(
    resource_uuid=os.environ["MC_RESOURCE_UUID"],
    resource_type="airflow",    # lowercase β€” e.g. airflow, dbt, coalesce, custom
    events=[job],               # 1–100 per batch
)
print("invocation_id:", service.extract_invocation_id(result))

A 202 response with an invocation_id means the push was accepted.

Push run events

from pycarlo.features.ingestion import EtlRunEvent, EtlError

# Successful run
run = EtlRunEvent(
    job_source_id="pipeline-123",
    run_source_id="run-2026-06-11-001",
    status="success",
    event_time="2026-06-11T02:30:00Z",
    start_time="2026-06-11T02:00:00Z",
    end_time="2026-06-11T02:30:00Z",
    trigger="SCHEDULE",
    run_url="https://airflow.example.com/dags/build_revenue/run/001",
    task_runs=[
        EtlRunEvent(
            job_source_id="pipeline-123",
            run_source_id="run-2026-06-11-001",
            task_source_id="extract-charges",
            status="success",
            event_time="2026-06-11T02:15:00Z",
            start_time="2026-06-11T02:00:00Z",
            end_time="2026-06-11T02:15:00Z",
        ),
        EtlRunEvent(
            job_source_id="pipeline-123",
            run_source_id="run-2026-06-11-001",
            task_source_id="build-model",
            status="success",
            event_time="2026-06-11T02:30:00Z",
            start_time="2026-06-11T02:15:00Z",
            end_time="2026-06-11T02:30:00Z",
        ),
    ],
)

result = service.send_etl_runs(
    resource_uuid=os.environ["MC_RESOURCE_UUID"],
    resource_type="airflow",
    events=[run],               # 1–100 per batch
)
print("invocation_id:", service.extract_invocation_id(result))

Reporting failures

For failed runs, set status to "failed" and include an error dict:

failed_run = EtlRunEvent(
    job_source_id="pipeline-123",
    run_source_id="run-2026-06-11-002",
    status="failed",
    event_time="2026-06-11T14:05:00Z",
    start_time="2026-06-11T14:00:00Z",
    end_time="2026-06-11T14:05:00Z",
    trigger="SCHEDULE",
    error=EtlError(
        message="Connection timeout after 30s",
        code="CONN_TIMEOUT",
        failure_type="infrastructure",
        retryable=True,
    ),
)

Option B β€” Direct HTTP

If you prefer not to use the pycarlo SDK, you can call the endpoints directly.

Base URL (production): https://integrations.getmontecarlo.com

Authentication headers:

HeaderValue
x-mcd-idIntegration key ID
x-mcd-tokenIntegration key secret
Content-Typeapplication/json

Push metadata

curl -X POST https://integrations.getmontecarlo.com/ingest/v1/etl/metadata \
  -H "x-mcd-id: $MC_INGEST_KEY_ID" \
  -H "x-mcd-token: $MC_INGEST_KEY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "resource_uuid": "'$MC_RESOURCE_UUID'",
    "resource_type": "airflow",
    "events": [
      {
        "job_source_id": "pipeline-123",
        "name": "Build revenue model",
        "description": "Daily revenue model build",
        "schedule": {
          "kind": "cron",
          "cron_expression": "0 2 * * *",
          "timezone": "UTC"
        }
      }
    ]
  }'

Push run events

curl -X POST https://integrations.getmontecarlo.com/ingest/v1/etl/runs \
  -H "x-mcd-id: $MC_INGEST_KEY_ID" \
  -H "x-mcd-token: $MC_INGEST_KEY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "resource_uuid": "'$MC_RESOURCE_UUID'",
    "resource_type": "airflow",
    "events": [
      {
        "job_source_id": "pipeline-123",
        "run_source_id": "run-001",
        "status": "success",
        "event_time": "2026-06-11T02:30:00Z",
        "start_time": "2026-06-11T02:00:00Z",
        "end_time": "2026-06-11T02:30:00Z",
        "trigger": "SCHEDULE"
      }
    ]
  }'

Response codes

Both endpoints return 202 Accepted on success:

{ "invocation_id": "<uuid>" }
StatusMeaning
400Schema validation failed. Check details in the response body for field-level errors.
401Authentication failed β€” wrong key ID or secret.
403The key is not authorized for the resource_uuid in the payload.
413Payload too large β€” compressed body exceeds 1 MB. Split into smaller batches.
5xxServer error β€” retry with exponential backoff.

4. View pipelines

Once data is pushed, pipelines appear in your Monte Carlo workspace. You can:

  • View job/pipeline status and run history
  • See task-level breakdown for each run
  • Trace cross-domain lineage connecting ETL pipelines to monitored warehouse tables (when inputs/outputs are populated)
  • Set up failure alerting on pipeline runs
  • Track run duration for SLA monitoring

Data schemas

Both the Push API and Custom ETL Connectors use the same schemas. The pycarlo SDK provides typed dataclasses, but you can also pass plain dicts with the same keys when using direct HTTP.

EtlAsset β€” pipeline/job metadata

Input to send_etl_metadata(). Describes the structural metadata of a job or pipeline.

FieldTypeRequiredDescription
job_source_idstryesStable vendor identifier for the job
namestryesHuman-readable job name
groupEtlGroupnoParent container (workspace, project, folder)
taskslist[EtlTask]noTask breakdown within the job
descriptionstrnoJob description
folderstrnoFolder path in vendor UI
is_pausedboolnoWhether the job is disabled
job_urlstrnoLink to the job in vendor UI
scheduleSchedulenoHow the job is triggered
ownerOwnernoJob ownership
propertieslist[Tag]noKey-value tags
attributesdictnoArbitrary vendor-specific metadata (passed through)
inputslist[AssetRef]noData assets the job reads (enables lineage)
outputslist[AssetRef]noData assets the job writes (enables lineage)
triggered_job_source_idslist[str]noDownstream jobs triggered by this one

EtlRunEvent β€” run execution data

Input to send_etl_runs(). Describes a specific execution of a job.

FieldTypeRequiredDescription
job_source_idstryesWhich job this run belongs to
run_source_idstryesVendor's unique run identifier
statusstryesRun status β€” see allowed values
event_timestryesISO 8601 timestamp with timezone β€” when this event was produced
start_timestrnoISO 8601 β€” when the run started
end_timestrconditionalISO 8601 β€” when the run ended. Required for terminal statuses.
triggerstrnoWhat started the run β€” see trigger types
run_urlstrnoLink to the run in vendor UI
task_source_idstrnoTask identifier (present only for task-level runs inside task_runs)
task_runslist[EtlRunEvent]noTask-level breakdown β€” each entry is itself an EtlRunEvent
errorEtlErrorconditionalError details. Required when status is failed or error.
inputslist[AssetRef]noRuntime lineage β€” assets actually read in this run
outputslist[AssetRef]noRuntime lineage β€” assets actually written in this run
attributesdictnoArbitrary vendor-specific metadata

Sub-schemas

EtlGroup

FieldTypeRequiredDescription
source_idstryesVendor's unique group identifier
namestrnoHuman-readable group name
group_typestrnoType label (e.g. "workspace", "project")
scheduleSchedulenoGroup-level schedule
attributesdictnoArbitrary metadata

EtlTask

FieldTypeRequiredDescription
task_source_idstryesVendor's unique task identifier
namestryesHuman-readable task name
task_typestrnoOperator/step type (e.g. "SqlOperator", "PythonOperator")
descriptionstrnoTask description
inputslist[AssetRef]noAssets this task reads
outputslist[AssetRef]noAssets this task writes
upstream_task_source_idslist[str]noTask dependencies within the job
triggered_job_source_idslist[str]noJobs triggered by this task
attributesdictnoArbitrary metadata

EtlError

FieldTypeRequiredDescription
messagestryesError message
codestrnoError code
failure_typestrnoCategory of failure
retryableboolnoWhether the error is retryable
upstream_failed_task_source_idslist[str]noTasks whose failure caused this one
structured_fieldsdictnoArbitrary structured error metadata

Schedule

FieldTypeRequiredDescription
kindstryescron, interval, event, upstream, or manual
cron_expressionstrnoCron expression (when kind is cron)
interval_secondsintnoInterval in seconds (when kind is interval)
timezonestrnoSchedule timezone
next_run_atstrnoISO 8601 next scheduled run
pausedboolnoWhether the schedule is paused
event_triggerdictnoEvent trigger details (when kind is event)
upstream_job_source_idslist[str]noUpstream dependencies (when kind is upstream)

Owner

FieldTypeRequiredDescription
primary_emailstrnoOwner's email
primary_namestrnoOwner's display name
primary_external_idstrnoExternal user ID
run_as_emailstrnoService account / execution identity
notification_emailslist[str]noAdditional notification recipients
teamstrnoOwning team name

AssetRef

FieldTypeRequiredDescription
asset_typestryesTABLE, VIEW, FILE, TOPIC, DATASET, or DASHBOARD
rolestryesINPUT or OUTPUT (must match the list it's in)
fully_qualified_namestrone of theseVendor-native asset name (e.g. db.schema.table)
mconstrone of theseMonte Carlo Object Name (internal identifier)

At least one of fully_qualified_name or mcon must be provided.

Tag

FieldTypeRequiredDescription
keystryesTag key
valuestryesTag value

Allowed enum values

Run statuses

success, failed, skipped, cancelled, in_progress, error, timed_out, queued, blocked, inactive, restarting, up_for_retry, up_for_reschedule, upstream_failed, removed, scheduled, deferred, pass, fail, warn, partial_success, unknown

Terminal statuses (require end_time): success, failed, skipped, cancelled, error, timed_out, pass, fail, partial_success

Failed statuses (require error): failed, error

Trigger types

SCHEDULE, MANUAL, API, UPSTREAM, EVENT, CYCLIC, BACKFILL, RETRY

Asset types

TABLE, FILE, VIEW, TOPIC, DATASET, DASHBOARD


Lineage

The inputs and outputs fields on EtlAsset, EtlTask, and EtlRunEvent enable cross-domain lineage in Monte Carlo:

  • Static lineage (on EtlAsset or EtlTask) β€” what a job/task always reads and writes. Declared once in metadata.
  • Runtime lineage (on EtlRunEvent) β€” what was actually read and written in a specific run. Use when lineage varies between runs (conditional logic, dynamic table selection).

When lineage is populated, Monte Carlo can:

  • Show which warehouse tables are produced by which ETL pipelines
  • Trace downstream impact when a pipeline fails
  • Connect ETL health to data quality monitors on the output tables

Lineage is entirely optional. If the vendor API doesn't expose input/output assets, omit the fields.


Validation rules

The pycarlo SDK enforces these rules at push time. If you use direct HTTP, the server validates on receipt.

Run events:

  • job_source_id, run_source_id, status, and event_time are required and non-empty
  • All datetime fields must be valid ISO 8601 with timezone
  • Terminal statuses require end_time
  • failed / error statuses require an error dict
  • task_runs are validated recursively (same rules)

Metadata events:

  • job_source_id and name are required and non-empty
  • group.source_id is required when group is provided
  • tasks[].task_source_id and tasks[].name are required when tasks are provided
  • inputs / outputs asset refs are validated at both asset and task level

General:

  • Batch size: 1–100 events per request
  • Omit None values and empty lists β€” the API expects sparse payloads

FAQs

What happens to my integration keys if I delete the integration?

Deleting an integration in Monte Carlo also deletes any integration keys (including Ingestion-scoped keys) associated with it. Any scripts or pipelines using those keys will start failing with 401 errors. Before deleting an integration, make sure no active push pipelines depend on its keys β€” or create new keys on a replacement integration first.


Did this page help you?