Schedule & Send
Set schedule intent, then create an execution job for a future send or an immediate send.
Base URL: https://api.experiture.ai/public/v1
Set Schedule Intent
Stores when a draft campaign should send. This does not publish, schedule, or send the campaign.
The request body is channel-dependent: direct_mail campaigns use a batch-date scheduling model; all other channels (email, SMS, push) use a timestamp model.
PUT /campaigns/:id/schedule
Authorization: Bearer <token>
Content-Type: application/jsonRequired scope: campaigns:update
Schedule intent can only be updated while the campaign is in draft status.
Digital channels (email, sms, push)
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | Yes | "now" or "at". |
sendAt | string | null | Conditional | ISO 8601 timestamp. Required when mode is "at". |
timezone | string | null | No | IANA timezone string. Defaults to "UTC". |
Example — email, future send
curl -X PUT https://api.experiture.ai/public/v1/campaigns/4130bada-9264-465f-bc0c-a26bebcfcc81/schedule \
-H "Authorization: Bearer <your_access_token>" \
-H "Content-Type: application/json" \
-d '{
"mode": "at",
"sendAt": "2026-05-01T09:00:00.000Z",
"timezone": "America/New_York"
}'Direct mail
Direct mail uses a date-only schedule — the channel's fulfillment pipeline does not accept an intraday time.
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | Yes | Must be "batch_on_date". |
batchDate | string | Yes | Target fulfillment date in YYYY-MM-DD format. |
timezone | string | null | No | IANA timezone used to interpret the date. Defaults to tenant default, then "UTC". |
Example — direct mail
curl -X PUT https://api.experiture.ai/public/v1/campaigns/4130bada-9264-465f-bc0c-a26bebcfcc81/schedule \
-H "Authorization: Bearer <your_access_token>" \
-H "Content-Type: application/json" \
-d '{
"mode": "batch_on_date",
"batchDate": "2026-05-01",
"timezone": "America/New_York"
}'Response — 200 OK — same shape as Get Campaign. The schedule object in the response includes a kind discriminator: "digital_send" for digital channels or "direct_mail_batch" for direct mail. See Schedule object variants below.
Errors
| Code | HTTP | Meaning |
|---|---|---|
JOURNEY_API.PUBLIC.CAMPAIGNS.NOT_FOUND | 404 | Campaign not found. |
JOURNEY_API.PUBLIC.CAMPAIGNS.NOT_DRAFT | 409 | Campaign is not a draft. |
JOURNEY_API.SCHEDULE.SEND_AT_REQUIRED | 400 | mode is "at" but sendAt is missing. |
JOURNEY_API.SCHEDULE.SEND_AT_INVALID | 400 | sendAt is present but malformed or in the past. |
JOURNEY_API.SCHEDULE.BATCH_DATE_REQUIRED | 400 | mode is "batch_on_date" but batchDate is missing. |
JOURNEY_API.SCHEDULE.BATCH_DATE_INVALID | 400 | batchDate is present but malformed or in the past. |
JOURNEY_API.SCHEDULE.UNSUPPORTED_CHANNEL | 400 | Channel is not supported for the requested schedule mode. |
JOURNEY_API.AUTH.INSUFFICIENT_SCOPE | 403 | Token lacks campaigns:update. |
Schedule Object Variants
The schedule object in campaign responses uses a kind discriminator to indicate which scheduling model applies.
Digital channels
{
"kind": "digital_send",
"mode": "at",
"sendAt": "2026-05-01T09:00:00.000Z",
"timezone": "America/New_York",
"scheduled": true
}Direct mail
{
"kind": "direct_mail_batch",
"mode": "batch_on_date",
"batchDate": "2026-05-01",
"timezone": "America/New_York",
"timezoneSource": "request",
"scheduled": true
}timezoneSource indicates how the timezone was resolved:
| Value | Meaning |
|---|---|
"request" | Timezone was explicitly supplied in the request. |
"tenant_default" | Timezone was not supplied; tenant default was applied. |
"utc_fallback" | No request timezone and no tenant default; UTC was used. |
Create Schedule Job
Commits a draft campaign to a future execution. For digital campaigns, if sendAt is omitted the API uses the saved schedule intent. For direct mail campaigns, use batchDate.
POST /campaigns/:id/schedule-jobs
Authorization: Bearer <token>
Content-Type: application/json
Idempotency-Key: <uuid-v4> (optional, 24-hour deduplication)Required scope: campaigns:schedule
The campaign must be a draft with at least one included audience and a published template bound before a schedule job can be created.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
sendAt | string | null | No | ISO 8601 timestamp for digital channels. If omitted, uses saved schedule intent. Not used for direct mail. |
batchDate | string | null | No | YYYY-MM-DD fulfillment date for direct mail. If omitted, uses saved schedule intent. Not used for digital channels. |
timezone | string | null | No | IANA timezone string. Defaults to saved schedule timezone or "UTC". |
dryRun | boolean | No | If true, validates and returns 200 without committing execution artifacts. |
Dry-run response — 200 OK
{
"success": true,
"data": {
"journeyId": "4130bada-9264-465f-bc0c-a26bebcfcc81",
"versionId": "11111111-1111-1111-1111-111111111111",
"status": "published",
"dryRun": true,
"warnings": [],
"bindings": [
{
"journey_version_id": "11111111-1111-1111-1111-111111111111",
"node_key": "send_1",
"manifest_id": "33333333-3333-3333-3333-333333333333",
"placeholder_manifest_id": null,
"touchpoint_id": 123,
"channel": "email"
}
]
}
}Committed response — 201 Created (digital)
{
"success": true,
"data": {
"journeyId": "4130bada-9264-465f-bc0c-a26bebcfcc81",
"versionId": "11111111-1111-1111-1111-111111111111",
"status": "published",
"warnings": [],
"bindings": [
{
"journey_version_id": "11111111-1111-1111-1111-111111111111",
"node_key": "send_1",
"manifest_id": "33333333-3333-3333-3333-333333333333",
"placeholder_manifest_id": null,
"touchpoint_id": 123,
"channel": "email"
}
],
"manifestId": "33333333-3333-3333-3333-333333333333",
"scheduleId": "44444444-4444-4444-4444-444444444444",
"touchpointId": 123,
"sendAt": "2026-05-01T09:00:00.000Z",
"timezone": "America/New_York"
}
}Committed response — 201 Created (direct mail)
{
"success": true,
"data": {
"journeyId": "4130bada-9264-465f-bc0c-a26bebcfcc81",
"versionId": "11111111-1111-1111-1111-111111111111",
"status": "published",
"warnings": [],
"bindings": [
{
"journey_version_id": "11111111-1111-1111-1111-111111111111",
"node_key": "send_1",
"manifest_id": "33333333-3333-3333-3333-333333333333",
"placeholder_manifest_id": null,
"touchpoint_id": 123,
"channel": "direct_mail"
}
],
"manifestId": "33333333-3333-3333-3333-333333333333",
"scheduleId": "44444444-4444-4444-4444-444444444444",
"touchpointId": 123,
"batchDate": "2026-05-01",
"timezone": "America/New_York"
}
}Create Send Job
Commits a draft campaign to immediate execution.
POST /campaigns/:id/send-jobs
Authorization: Bearer <token>
Content-Type: application/json
Idempotency-Key: <uuid-v4> (optional, 24-hour deduplication)Required scope: campaigns:send
Not supported for direct mail. This endpoint returns 400 JOURNEY_API.SCHEDULE.INVALID_FOR_CHANNEL when the campaign channel is direct_mail. Direct mail requires a fulfillment date and cannot be triggered as an immediate send. Use POST /schedule-jobs with a batchDate instead.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
dryRun | boolean | No | If true, validates and returns 200 without committing execution artifacts. |
Response — 201 Created for committed sends, 200 OK for dry runs. Same shape as Create Schedule Job.
Execution Errors
| Code | HTTP | Meaning |
|---|---|---|
JOURNEY_API.PUBLIC.CAMPAIGNS.NOT_FOUND | 404 | Campaign not found. |
JOURNEY_API.PUBLIC.CAMPAIGNS.TYPE_UNSUPPORTED | 409 | Only broadcast campaigns can be scheduled or sent in v1. |
JOURNEY_API.PUBLIC.CAMPAIGNS.NOT_DRAFT | 409 | Campaign is not a draft. |
JOURNEY_API.PUBLIC.CAMPAIGNS.CONTENT.REQUIRED | 409 | Published template/content is not configured. |
JOURNEY_API.PUBLIC.CAMPAIGNS.AUDIENCE.REQUIRED | 409 | At least one included audience is required. |
JOURNEY_API.PUBLIC.CAMPAIGNS.VERSION_MISSING | 409 | Campaign has no current version. |
JOURNEY_API.SCHEDULE.SEND_AT_REQUIRED | 400 | Future schedule execution has no send time. |
JOURNEY_API.SCHEDULE.SEND_AT_INVALID | 400 | sendAt is malformed or in the past. |
JOURNEY_API.SCHEDULE.BATCH_DATE_REQUIRED | 400 | Direct mail schedule has no batch date. |
JOURNEY_API.SCHEDULE.BATCH_DATE_INVALID | 400 | batchDate is malformed or in the past. |
JOURNEY_API.SCHEDULE.INVALID_FOR_CHANNEL | 400 | Operation is not permitted for this campaign's channel (e.g. immediate send on direct mail). |
JOURNEY_API.SCHEDULE.UNSUPPORTED_CHANNEL | 400 | Channel is not supported for the requested schedule mode. |
JOURNEY_API.SCHEDULE.UNSUPPORTED_TYPE | 500 | Internal: unsupported schedule type encountered. Contact support if this persists. |
JOURNEY_API.BROADCASTS.SCHEDULE.PREFLIGHT_BLOCKERS | 400 | Preflight found blocking issues. |
JOURNEY_API.AUTH.INSUFFICIENT_SCOPE | 403 | Token lacks the required scope. |
Schedule vs Send
POST /schedule-jobs | POST /send-jobs | |
|---|---|---|
| Scope | campaigns:schedule | campaigns:send |
| Timing | Future sendAt (digital) or batchDate (direct mail) | Immediate |
| Channels supported | All (email, sms, push, direct_mail) | Digital only (email, sms, push) |
| Uses saved schedule intent | Yes, when sendAt/batchDate is omitted | No |
| dryRun supported | Yes | Yes |
| Idempotency | Yes | Yes |
See Also
- Preflight - validate readiness before execution
- Status & Metrics - observe execution after send
- Campaign Object - create and configure the campaign