Skip to main content

Actions

Actions are the core primitive in CallMeLater. An action is either a scheduled HTTP request (webhook mode) or a human approval request (approval mode) that executes at a specified time.

Create Action

POST /actions

Common Fields

FieldTypeRequiredDescription
modestringNowebhook (default) or approval
namestringNoDisplay name (auto-generated if omitted)
descriptionstringNoOptional description (max 1000 chars)
timezonestringNoIANA timezone for scheduling (e.g., America/New_York). Defaults to UTC.
idempotency_keystringNoUnique key to prevent duplicate creation (max 255 chars)
callback_urlstringNoURL to receive lifecycle events (executed, failed, expired)

Schedule Fields

At least one scheduling field is required.

FieldTypeExampleDescription
schedule.presetstring"tomorrow"Named time preset
schedule.waitstring"2h"Relative wait duration from now
scheduled_forstring"2026-04-01T09:00:00Z"Exact UTC timestamp (top-level field)

Presets: tomorrow, next_week, next_monday through next_sunday, 1h, 2h, 4h, 1d, 3d, 1w

Wait format: Number followed by a unit -- m (minutes), h (hours), d (days), w (weeks). Examples: 30m, 2h, 14d, 1w.

Webhook Mode Fields

FieldTypeRequiredDescription
requestobjectYesHTTP request configuration
request.urlstringYesDestination URL (HTTPS required in production)
request.methodstringNoHTTP method (default: POST)
request.headersobjectNoCustom headers as key-value pairs
request.bodyobjectNoJSON request body
max_attemptsintegerNoMaximum delivery attempts, 1-10 (default: 5)
retry_strategystringNoexponential (default) or linear
webhook_secretstringNoSecret used to sign the outgoing request

Approval Mode Fields

FieldTypeRequiredDescription
gateobjectYesApproval gate configuration
gate.messagestringYesMessage displayed to recipients (max 5000 chars)
gate.recipientsarrayYesEmail addresses, phone numbers (E.164), or contact IDs
gate.channelsarrayNoDelivery channels: ["email"] (default) or ["email", "sms"]
gate.confirmation_modestringNofirst_response (default) or all_required
gate.max_snoozesintegerNoMaximum snooze count, 0-10 (default: 5)
gate.timeoutstringNoResponse timeout, e.g. 4h, 7d, 1w (default: 7d)
gate.escalation_contactsarrayNoEmail addresses for escalation
gate.escalation_after_hoursnumberNoHours before escalating (min: 0.5)
gate.attachmentsarrayNoFile attachments for the approval message
requestobjectNoOptional HTTP request to execute after approval is granted

Dedup Keys Fields

Group related actions and control their behavior with dedup keys.

FieldTypeRequiredDescription
dedup_keysarrayNoGrouping keys (max 10). Alphanumeric plus _, :, ., -.
coordinationobjectNoDedup behavior configuration
coordination.on_createstringNoskip_if_exists or cancel_and_replace
coordination.on_executeobjectNoExecution-time conditions
coordination.on_execute.conditionstringNoskip_if_previous_pending, execute_if_previous_failed, execute_if_previous_succeeded, wait_for_previous
coordination.on_execute.on_condition_not_metstringNocancel (default), reschedule, fail
coordination.on_execute.reschedule_delayintegerNoSeconds before retry, 60-86400 (default: 300)
coordination.on_execute.max_reschedulesintegerNoMax reschedule attempts, 1-100 (default: 10)

on_create behaviors:

  • skip_if_exists -- Return the existing non-terminal action instead of creating a new one. The response uses status 200 with meta.skipped: true.
  • cancel_and_replace -- Cancel all existing non-terminal actions sharing the same dedup keys, then create the new action.

on_execute conditions:

  • skip_if_previous_pending -- Cancel this action if another non-terminal action with the same key exists.
  • execute_if_previous_failed -- Only execute if the most recent action with the same key failed.
  • execute_if_previous_succeeded -- Only execute if the most recent action with the same key succeeded.
  • wait_for_previous -- Reschedule until the previous action with the same key reaches a terminal state.

Example: Webhook Mode

curl -X POST https://api.callmelater.io/v1/actions \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"mode": "webhook",
"name": "Trial expiration webhook",
"idempotency_key": "trial-end-user-42",
"schedule": { "wait": "14d" },
"request": {
"method": "POST",
"url": "https://api.example.com/webhooks/trial-expired",
"headers": { "X-Custom-Header": "value" },
"body": { "event": "trial_expired", "user_id": 42 }
},
"max_attempts": 5,
"retry_strategy": "exponential"
}'

Example: Approval Mode

curl -X POST https://api.callmelater.io/v1/actions \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"mode": "approval",
"name": "Production deployment approval",
"idempotency_key": "deploy-approval-abc123",
"schedule": { "wait": "30m" },
"gate": {
"message": "Please approve the production deployment for release v2.1.0",
"recipients": ["tech-lead@example.com", "+15551234567"],
"channels": ["email", "sms"],
"timeout": "4h",
"max_snoozes": 3
},
"callback_url": "https://api.example.com/webhooks/approval-response"
}'

Responses

201 Created

{
"data": {
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "Trial expiration webhook",
"description": null,
"mode": "webhook",
"status": "scheduled",
"timezone": "UTC",
"scheduled_for": "2026-02-04T10:30:00Z",
"idempotency_key": "trial-end-user-42",
"dedup_keys": [],
"attempt_count": 0,
"max_attempts": 5,
"retry_strategy": "exponential",
"created_at": "2026-01-21T10:30:00Z",
"updated_at": "2026-01-21T10:30:00Z"
}
}

200 Skipped (when coordination.on_create is skip_if_exists and a matching action exists)

{
"data": {
"id": "existing-action-id",
"name": "Existing action",
"status": "scheduled"
},
"meta": {
"skipped": true,
"reason": "existing_action_found"
}
}

422 Validation Error

{
"message": "The given data was invalid.",
"errors": {
"request.url": ["The request.url field is required."]
}
}

List Actions

GET /actions

Query Parameters

ParameterTypeDefaultDescription
statusstring--Filter by status: scheduled, resolved, executing, awaiting_response, executed, failed, cancelled, expired
modestring--Filter by mode: webhook or approval
searchstring--Search in name and description
dedup_keystring--Filter by dedup key
per_pageinteger15Results per page (max 100)
pageinteger1Page number

Response

{
"data": [
{
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "Trial expiration webhook",
"mode": "webhook",
"status": "scheduled",
"scheduled_for": "2026-02-04T10:30:00Z",
"idempotency_key": "trial-end-user-42",
"dedup_keys": [],
"created_at": "2026-01-21T10:30:00Z",
"updated_at": "2026-01-21T10:30:00Z"
}
],
"meta": {
"current_page": 1,
"last_page": 5,
"total": 72
}
}

Get Action

GET /actions/{id}

Returns the full action detail including delivery history and approval events.

Response Fields

FieldTypeDescription
idstringAction UUID
namestringDisplay name
modestringwebhook or approval
statusstringCurrent status
scheduled_forstringScheduled execution time (ISO 8601)
executed_atstringActual execution time (if completed)
requestobjectHTTP request config (webhook mode)
gateobjectApproval gate config (approval mode)
delivery_attemptsarrayHTTP delivery attempt log (webhook mode)
reminder_eventsarrayApproval event timeline (approval mode)
dedup_keysarrayDedup keys assigned to this action
created_atstringCreation timestamp
updated_atstringLast update timestamp

Cancel Action

DELETE /actions/{id}

Cancel a pending action before it executes.

Cancellable statuses: scheduled, resolved, awaiting_response

Actions that have already reached a terminal status (executed, failed, expired) cannot be cancelled.

Response

200 OK

{
"data": {
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "Trial expiration webhook",
"status": "cancelled"
}
}

Cancel by Idempotency Key

You can also cancel an action using its idempotency key instead of the UUID:

DELETE /actions
{
"idempotency_key": "trial-end-user-42"
}

Retry Action

POST /actions/{id}/retry

Manually retry a failed action. This resets the attempt counter and creates a new execution cycle.

Requirements

  • The action must be in failed status
  • The action must have an HTTP request configuration (webhook mode)

Response

200 OK

{
"data": {
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "Failed webhook",
"status": "resolved",
"attempt_count": 0,
"manual_retry_count": 1
}
}

422 Unprocessable Entity (if the action is not in failed status or has no HTTP config)


List Coordination Keys

GET /coordination-keys

Returns a list of all dedup keys used across your account. Use this to discover keys, then filter actions by a specific key using the dedup_key parameter on the List Actions endpoint.

Response

{
"keys": [
"deploy:api-service",
"deploy:web-app",
"migration:db-upgrade",
"user:42:trial"
]
}