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 -- s (seconds), m (minutes), h (hours), d (days), w (weeks). Examples: 30s, 5m, 2h, 14d, 1w.

Sub-minute scheduling

Delays under 5 minutes are dispatched with second-level precision via a Redis queue, bypassing the minute-based scheduler. This requires a Redis queue connection and a worker processing the fast queue.

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, sms, teams, slack, push. Defaults to ["email"].
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.on_timeoutstringNoWhat happens when the timeout expires: cancel (default), expire, or approve
gate.escalationobjectNoEscalation configuration
gate.escalation.contactsarrayNoEmail addresses or phone numbers 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

Recurrence Fields​

Make an action repeat on a schedule. After each successful execution, the action automatically re-schedules itself.

FieldTypeRequiredDescription
recurrence.frequencyintegerYesHow often to repeat (min: 1)
recurrence.unitstringYesTime unit: m (minutes), h (hours), d (days), w (weeks), M (months)
recurrence.end_typestringYesWhen to stop: never, count, or date
recurrence.max_occurrencesintegerConditionalRequired when end_type is count. Total executions (min: 2).
recurrence.end_datestringConditionalRequired when end_type is date. ISO 8601 timestamp, must be in the future.

The minimum recurrence interval is 5 minutes.

You can also use repeat as an alias for recurrence.

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 replace_existing
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.
  • replace_existing -- 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://callmelater.io/api/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://callmelater.io/api/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"
}'

Example: Recurring Webhook​

curl -X POST https://callmelater.io/api/v1/actions \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"mode": "webhook",
"name": "Hourly health check",
"schedule": { "wait": "5m" },
"request": {
"method": "POST",
"url": "https://api.example.com/health-check",
"body": { "source": "callmelater" }
},
"recurrence": {
"frequency": 1,
"unit": "h",
"end_type": "count",
"max_occurrences": 24
}
}'

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",
"is_recurring": false,
"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 resolution status: pending_resolution, resolved, executing, awaiting_response, executed, failed, cancelled, expired
modestring--Filter by mode: immediate or gated
searchstring--Search in name and description
coordination_keystring--Filter by coordination key
recurringstring--Filter by recurrence: recurring or one-time
per_pageinteger25Results 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)
recipientsarrayRecipient list with response details (approval mode)
dedup_keysarrayDedup keys assigned to this action
is_recurringbooleanWhether this action repeats
recurrenceobjectRecurrence config (when recurring)
recurrence_countintegerNumber of completed executions (when recurring)
last_executed_atstringTimestamp of last execution (when recurring)
created_atstringCreation timestamp
updated_atstringLast update timestamp

Reminder Events Object​

Each entry in the reminder_events array represents a lifecycle event for the approval.

FieldTypeDescription
idstringEvent UUID
event_typestringsent, confirmed, declined, snoozed, escalated, or expired
actor_emailstringEmail of the person who triggered the event (null for system events)
notesstringOptional comment left by the responder
created_atstringEvent timestamp (ISO 8601)

Recipients Object​

Each entry in the recipients array represents one recipient and their response.

FieldTypeDescription
idstringRecipient UUID
emailstringRecipient email or phone number
statusstringpending, confirmed, declined, or snoozed
responded_atstringWhen the recipient responded (ISO 8601, null if pending)
response_commentstringOptional comment left by the recipient (max 500 chars, null if none)
display_namestringDisplay name (from contact or auto-detected)

Example: Approval Response Detail​

{
"data": {
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "Validate legal answer",
"mode": "approval",
"status": "executed",
"scheduled_for": "2026-03-15T09:00:00Z",
"executed_at": "2026-03-15T09:12:34Z",
"recipients": [
{
"id": "recipient-uuid",
"email": "notaire@example.com",
"status": "confirmed",
"responded_at": "2026-03-15T09:12:34Z",
"response_comment": "Verified. This is correct per Article 1134 of the Civil Code.",
"display_name": "Me Dupont"
}
],
"reminder_events": [
{
"id": "event-uuid-1",
"event_type": "sent",
"actor_email": null,
"notes": null,
"created_at": "2026-03-15T09:00:00Z"
},
{
"id": "event-uuid-2",
"event_type": "confirmed",
"actor_email": "notaire@example.com",
"notes": "Verified. This is correct per Article 1134 of the Civil Code.",
"created_at": "2026-03-15T09:12:34Z"
}
]
}
}

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"
}

Cancel by Coordination Key​

Cancel all non-terminal actions sharing a coordination (dedup) key. This is useful for batch cancellation -- for example, cancelling everything related to a deployment or a user flow.

DELETE /actions/by-coordination-key
{
"coordination_key": "deploy:prod"
}

Actions in terminal states (executed, failed, cancelled, expired) are skipped. Only actions that are still pending, scheduled, or awaiting a response are cancelled.

200 OK

{
"message": "3 actions cancelled",
"cancelled": 3,
"skipped": 0,
"coordination_key": "deploy:prod"
}

404 Not Found -- No cancellable actions match the key (or the key doesn't exist in your account).


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"
]
}