API Reference
Campaigns
Campaign Runs

Campaign Runs

A run represents a single dispatch of a published campaign to an audience.

Runs are generated in two ways:

  • Scheduled Runs: The campaign's initial run, created automatically when you publish via POST /schedule-jobs or POST /send-jobs.
  • On-Demand Runs: Additional runs triggered manually or programmatically via POST /campaigns/:id/runs. This is used for rolling enrollment cycles, follow-up sends, or triggering a new direct mail drop for a refreshed audience.

This page documents how to retrieve the history of a campaign's runs and how to trigger new ones via the API.

Base URL: https://api.experiture.ai/public/v1


List Campaign Runs

Returns the most recent runs for a campaign, including their current execution status. Useful for operational dashboards, polling for completion, or retrieving generated export files.

GET /campaigns/:id/runs?limit=25
Authorization: Bearer <token>

Required scope: campaigns:read

Path parameters

NameTypeDescription
campaign_idstring (UUID)Campaign identifier.

Query parameters

NameTypeDescription
limitinteger1–50, default 25. Limits the number of runs returned.

Response — 200 OK

Note: To keep this example concise, the full package schema is omitted from the JSON below. See Advanced: Execution Packages for the complete package and artifact schema.

{
  "success": true,
  "data": {
    "campaignId": "4130bada-9264-465f-bc0c-a26bebcfcc81",
    "name": "Spring Re-engagement Direct Mail",
    "channel": "direct_mail",
    "manifestId": "33333333-3333-3333-3333-333333333333",
    "touchpointId": 123,
    "runs": [
      {
        "runId": "5e5e5e5e-5e5e-5e5e-5e5e-5e5e5e5e5e5e",
        "scheduleRunId": "5e5e5e5e-5e5e-5e5e-5e5e-5e5e5e5e5e5e",
        "scheduleId": "44444444-4444-4444-4444-444444444444",
        "manifestId": "33333333-3333-3333-3333-333333333333",
        "touchpointId": 123,
        "channel": "direct_mail",
        "runKind": "on_demand",
        "runStatus": "enqueued",
        "requestSource": "api",
        "requestedAt": "2026-05-09T13:40:11.000Z",
        "unitCount": 17480,
        "packageStatus": "ready",
        "package": {}
      }
    ]
  }
}

If the campaign has no manifest yet (never finalized) the response returns runs: [].

Run object fields

FieldTypeDescription
runIdstring (UUID)Stable run identifier (alias of scheduleRunId).
scheduleRunIdstring (UUID)Internal identifier (matches runId).
scheduleIdstring (UUID)The bound schedule that produced this run.
manifestIdstring (UUID)Composition manifest the run executed against.
touchpointIdintegerTouchpoint number within the campaign manifest.
channelstringNormalized channel (email, sms, push, direct_mail, landing_page).
runKindstringHow the run was categorized — see Run Kinds.
runStatusstringLatest known execution status — see Run Status.
requestSourcestringOrigin of the run — see Request Sources.
requestedAtstringISO 8601 timestamp the run was queued.
unitCountinteger | nullTotal number of recipients or mailpieces processed in this run.
packageStatusstring | nullStatus of the export package, if applicable.
packageobject | nullOnly present when the run generates physical artifacts (e.g., Direct Mail drops). See Advanced: Execution Packages.

Trigger a New Run

Requests a new run against an already published campaign. For example, use this to drop another batch of a Direct Mail piece against an updated audience snapshot, to enqueue a rolling-enrollment cycle, or to trigger an on-demand re-send to a freshly-suppressed cohort.

POST /campaigns/:id/runs
Authorization: Bearer <token>
Content-Type: application/json
Idempotency-Key: <uuid-v4>   (optional, 24-hour deduplication)

Required scope: campaigns:update

⚠️

Published only. The campaign must already be in authoringStatus: "published" (i.e. its initial schedule-job has finalized). Run requests against drafts return 409 NOT_PUBLISHED.

Backpressure. A campaign can have at most 5 active run requests (statuses pending, claiming, enqueued) processing at one time. Additional requests return 429 BACKPRESSURE until the worker drains the queue.

Path parameters

NameTypeDescription
campaign_idstring (UUID)Campaign identifier.

Request body

FieldTypeRequiredDescription
audienceobject | nullNoOptional audience override for this run only. If omitted, the run reuses the published campaign's original targeting receipt. If provided, include must contain at least one entry.
requestSourceenumNoThe system or actor triggering the run. One of "api", "platform", "automation", or "integration". Defaults to "api".
requestKindenumNoThe operational purpose of the run. In v1 the only accepted value is "on_demand" (also the default). Sending any other value returns 400 JOURNEY_API.VALIDATION.BODY_SCHEMA_MISMATCH. Additional kinds (e.g. recurring-enrollment, imported-list, and automation sources) will be added alongside their execution adapters in a future version.
idempotencyKeystring | nullNoA caller-supplied key. If omitted, the API uses the Idempotency-Key request header.
metadataobject | nullNoArbitrary integration metadata stored alongside the run request and surfaced on the run object.

audience object

FieldTypeRequiredDescription
includearrayYes1–25 audience/segment entries ({ audienceId, type }).
excludearrayNo0–25 suppression entries ({ audienceId, type }).

Response — 202 Accepted

{
  "success": true,
  "data": {
    "runRequestId": "7c3e6a2a-1d4f-49ab-aabb-ccccccccdddd",
    "runId": "5e5e5e5e-5e5e-5e5e-5e5e-5e5e5e5e5e5e",
    "scheduleRunId": "5e5e5e5e-5e5e-5e5e-5e5e-5e5e5e5e5e5e",
    "campaignId": "4130bada-9264-465f-bc0c-a26bebcfcc81",
    "scheduleId": "44444444-4444-4444-4444-444444444444",
    "manifestId": "33333333-3333-3333-3333-333333333333",
    "touchpointId": 123,
    "channel": "direct_mail",
    "status": "pending",
    "requestSource": "api",
    "requestKind": "on_demand",
    "dueAt": "2026-05-09T13:40:11.000Z",
    "requestedAt": "2026-05-09T13:40:11.000Z"
  }
}

The request is queued asynchronously. To observe the run's progress, poll GET /campaigns/:id/runs and read the matching runId's runStatus property.

Errors

CodeHTTPMeaning
JOURNEY_API.CAMPAIGN_RUNS.INVALID_ID400The supplied campaign_id is not a valid UUID.
JOURNEY_API.VALIDATION.BODY_SCHEMA_MISMATCH400The request body failed schema validation — e.g. a requestKind other than on_demand, or an unknown field.
JOURNEY_API.CAMPAIGN_RUNS.AUDIENCE_REQUIRED400 / 409The request supplied an empty audience.include, or the published campaign has no default targeting receipt to inherit.
JOURNEY_API.CAMPAIGN_RUNS.TARGETING_RECEIPT_MISSING409The published campaign is still finalizing its initial audience—wait for the original schedule-job's finalization to complete and try again.
JOURNEY_API.CAMPAIGN_RUNS.NOT_FOUND404Campaign not found in the token tenant.
JOURNEY_API.CAMPAIGN_RUNS.NOT_PUBLISHED409Campaign exists but is not in published status.
JOURNEY_API.CAMPAIGN_RUNS.MANIFEST_MISSING409The published campaign has no composition manifest mapping. Internal data inconsistency — contact support.
JOURNEY_API.CAMPAIGN_RUNS.MANIFEST_INCOMPLETE409The composition manifest exists but lacks the schedule_id needed to bind a run.
JOURNEY_API.CAMPAIGN_RUNS.SCHEDULE_INACTIVE409The bound schedule is inactive. Re-activate or reschedule the campaign before requesting more runs.
JOURNEY_API.CAMPAIGN_RUNS.BACKPRESSURE429Campaign already has 5 active run requests processing. Retry after one of them resolves.
JOURNEY_API.CAMPAIGN_RUNS.CREATE_FAILED500The run request could not be persisted. Safe to retry with the same Idempotency-Key.
JOURNEY_API.AUTH.INSUFFICIENT_SCOPE403Token lacks campaigns:update.

Run Metadata Reference

Run Kinds

runKind on a run object records how the run originated. It is a response field — you don't set it directly. (The requestKind you send on POST /runs is a separate field, and in v1 only accepts on_demand.)

ValueMeaning
scheduledThe run came from the campaign's original POST /schedule-jobs or POST /send-jobs — the first send of the campaign.
on_demandThe run came from a POST /campaigns/:id/runs request.
delivery_runA digital (email/SMS/push) execution package produced by the run.
production_dropA Direct Mail execution package produced by the run.
activation_runA landing-page activation package produced by the run.

Run Status

A run progresses through these states, visible on runs[].runStatus:

ValueMeaning
pendingThe run request has been accepted but not yet claimed by the worker.
claimingA worker is in the process of claiming the request and reserving execution slots.
enqueuedThe run is actively enqueued in the execution pipeline.
failedThe worker rejected or could not place the run.
cancelledThe run was cancelled (administratively or via tenant action) before it dispatched.

A failed or cancelled run does not block additional run requests — but it does count toward the 5-active-request backpressure limit until the worker clears it.

Request Sources

requestSource records who or what created the run, so downstream audit reports can distinguish them:

ValueDescription
apiPublic API consumer (this endpoint, default).
platformExperiture platform UI created the request.
automationInternal automation rail (e.g., journey-driven re-runs).
integrationPre-built integration (CRM sync, marketing automation connector).
scheduleSynthetic source attributed to the campaign's original, implicit first run. Read-only — not accepted on POST /campaigns/:id/runs.

Run Lifecycle Diagram

Whether a run is created automatically by a schedule or manually via the API, the execution lifecycle is identical.

POST /schedule-jobs                  POST /campaigns/:id/runs
       │                                       │
       │  (initial, finalizes asynchronously)  │  (additional, on a published campaign)
       ▼                                       ▼
   run #0  ─────────►  enqueued  ────►  package: pending → assembling → ready → handed_off

       └──► failed                  ─────►  package: failed (failureKind populated)

Advanced: Execution Packages & Exports

Unlike digital channels (Email, SMS) which are event-driven, channels like Direct Mail require the platform to generate secure export files (PDFs, USPS manifest CSVs, Intelligent Mail Barcodes).

These files are bundled into an Execution Package. When querying /campaigns/:id/runs for a campaign that generates physical artifacts, the response will include a package object containing secure URIs to these files.

ChannelpackageKindWhat it carries
email, sms, pushdelivery_runTypically null. Event-driven.
direct_mailproduction_dropRoster, Intelligent Mail Barcode (IMb) payloads, fold/insert manifest, permit indicia metadata.
landing_pageactivation_runActivation event roster, per-piece personalization, page-variant resolution.

Package Status

If a package is generated, it goes through its own lifecycle independent of the overall run status:

ValueMeaning
pendingRun request has been queued; the package row exists but is empty.
assemblingThe platform is materializing artifacts (rosters, IMb data, content drops).
readyAll artifacts are written and the package is ready for handoff to the downstream provider.
handed_offThe package has been delivered to the downstream provider (e.g., postal print provider).
failedPackage assembly failed. failureKind, lastErrorCode, and lastErrorMessage are populated.
cancelledThe package was cancelled before handoff.

failureKind is one of upstream_execution, finalizer_aggregation, handoff, artifact_write, provider.

Package Object Fields

FieldTypeDescription
executionPackageIdstring (UUID)Stable package identifier.
executionIdstring (UUID) | nullLatest execution row that produced or updated this package. null while pending.
scheduleRunIdstring (UUID)Run that produced the package.
packageKindenumdelivery_run / production_drop / activation_run.
packageStatusenumSee Package Status above.
unitCountintegerNumber of units in the package.
packageRootUristring | nullTenant-scoped storage prefix. Always one of abfss://, https://, s3://, production/, or packages/.
directMailProductionPackageIdstring | nullDirect-mail-only — the underlying production-package identifier.
failureKindenum | nullWhen packageStatus is failed, one of upstream_execution, finalizer_aggregation, handoff, artifact_write, provider.
lastErrorCode, lastErrorMessagestring | nullDiagnostic detail when failed.
metadataobjectChannel-specific metadata.
channelPackageobject | nullFor Direct Mail: { type: "direct_mail_production_package", id, status }.
artifactsarrayIndividual artifact files (roster, IMb data, content drops, etc.). See below.
createdAt, updatedAtstring | nullISO 8601 timestamps.

Artifact Object Fields

Each entry in the package.artifacts[] array represents a distinct file generated for the run:

FieldTypeDescription
artifactIdstringArtifact identifier.
rolestringWhat the artifact represents (e.g. delivery_roster, intelligent_mail_barcodes, content_drop).
uristringTenant-scoped storage URI. Same prefix rules as packageRootUri.
contentTypestring | nullMIME type.
rowCountinteger | nullRow count for tabular artifacts.
byteSizestring | nullDecimal string (avoids JSON-number precision loss for large files).
checksumSha256string | nullLowercase SHA-256 of the artifact bytes.
piiClassificationenumData sensitivity. One of none, contact, phone, address, postal_identity, message_content, tokenized_identifier.
metadataobjectArtifact-specific metadata.
createdAtstring | nullISO 8601 write timestamp.

Storage URIs are tenant-scoped. The uri field is the canonical reference to the exported file, but it is only readable from within an Experiture-managed compute environment that has the tenant's data-plane credentials. Use a tenant-scoped credential broker (e.g., via the connectors framework) — never share these URIs directly with end-user clients.


See Also