Skip to content

Schedule a job (POST /api/jobs)

POST https://app.delay.run/api/jobs schedules one HTTP request to run now or up to 24 hours later. Delay sends it with the timeout and retries of the named queue, and creates the queue if it doesn’t exist.

Minimal request

Terminal window
curl -X POST https://app.delay.run/api/jobs \
-H "Authorization: Bearer $DELAY_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"queue": "emails", "url": "https://api.yourapp.com/send-welcome"}'

This sends POST https://api.yourapp.com/send-welcome right away, with the body []. Your token is under Settings > API token (authentication).

Request fields

FieldTypeRequiredDefaultLimits and notes
queuestringYes-Max 255 characters. A queue in your current team. Created with default settings if it doesn’t exist.
urlstringYes-A valid URL, max 2,048 characters. Private and reserved addresses fail when the job runs, not here.
methodstringNoPOSTGET, POST, PUT, PATCH or DELETE, in uppercase.
headersobjectNoNoneHeader names with string values, sent as given.
payloadobject or arrayNoNoneSent as JSON, or as the query string for GET. A string or number is rejected.
callback_urlstringNoNoneA valid URL, max 2,048 characters. Receives a POST when the job succeeds or is dead-lettered (callbacks).
delayintegerNo0Seconds before the first attempt, from 0 to 86,400 (24 hours).

The timeout, retries, concurrency limit and assertions come from the queue. You can’t set them per job, and this endpoint can’t change them (queue settings).

How the payload is sent

MethodWhat Delay sends
GETNo body. Payload keys are added to the URL’s query string. A payload key replaces a URL parameter with the same name.
POST, PUT, PATCHA JSON body. With no payload (or an empty one) the body is [].
DELETEA JSON body when there’s a payload, otherwise no body.

A request with a body carries Content-Type: application/json, unless your headers set a different one. GET, and DELETE without a payload, have no body and no Content-Type. If your team has a signing secret, Delay also adds X-Delay-Signature and X-Delay-Timestamp (verify requests).

Examples

Send a welcome email 15 minutes after signup, with a custom header and a callback:

Terminal window
curl -X POST https://app.delay.run/api/jobs \
-H "Authorization: Bearer $DELAY_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"queue": "emails",
"url": "https://api.yourapp.com/send-welcome",
"method": "POST",
"headers": {"X-Source": "delay"},
"payload": {"user_id": 42, "template": "welcome"},
"callback_url": "https://api.yourapp.com/delay-callback?user=42",
"delay": 900
}'

Response

201 Created

{
"success": true,
"queue_id": "019a0c4e-1f3b-7c55-8a1e-6d2f9b0c4a71",
"job_id": "019a0c4e-7b21-7d3a-9f4e-2c1b8a6d5e40",
"scheduled_at": "2026-10-18T14:15:00+00:00",
"message": "Scheduled successfully"
}
FieldMeaning
successAlways true.
queue_idId of the queue the job was added to.
job_idId of attempt 1. Each retry gets a new id (why).
scheduled_atThe earliest time the job runs: now plus delay, in ISO 8601 (UTC).
messageAlways Scheduled successfully.

A 201 doesn’t mean your team has requests left this month. Over the limit, the job is still accepted, then fails when it runs, with no retries and no callback (usage).

Errors

401: missing or invalid token

Always JSON, whatever the Accept header:

{"error": "Missing authorization token"}
{"error": "Invalid webhook token"}

The first means there was no Authorization: Bearer header. The second means the token didn’t match, for example because it was regenerated. “Webhook token” here means your API token.

422: validation error

{
"message": "A queue name is required. (and 1 more error)",
"errors": {
"queue": ["A queue name is required."],
"delay": ["The delay cannot exceed 86400 seconds (24 hours)."]
}
}

message is the first error plus a count of the rest. errors lists the messages for each field.

  • Validation runs before the token check, so a bad body gets a 422 even when the token is missing or wrong.
  • You only get a 422 when you send Accept: application/json. Without it, a validation error comes back as a 302 redirect with no details. Content-Type: application/json alone isn’t enough.

429: rate limit

The limit is 60 requests per minute per client IP address. POST /api/jobs and POST /api/crons share it, and it isn’t per token or per team, so everything you run behind one IP address shares one budget. See API overview and authentication.

A 429 response has the headers Retry-After (seconds to wait), X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. With Accept: application/json the body is:

{"message": "Too Many Attempts."}

Successful responses carry X-RateLimit-Limit and X-RateLimit-Remaining too, so you can slow down before you hit the limit. To schedule many jobs, send them one at a time and, after a 429, wait Retry-After seconds before you continue.

What this endpoint can’t do

  • Read, list, update or cancel jobs. No other job endpoints exist. To cancel a job, delete it while it’s pending (how).
  • Run at an absolute time. Work out delay yourself, up to 24 hours ahead.
  • Set a timeout, retries or assertions per job, or create and configure queues. Those are queue settings in the dashboard.
  • Create several jobs in one request.
  • Deduplicate. Every call creates a new job, so if you resend a call that timed out on your side, the job may be scheduled twice.
  • Return the job’s result. Use callback_url, or check Jobs in the dashboard.

Next steps