API Reference
Complete endpoint reference for the Warpflow Signals REST API—webhooks, AI actions, email, conversations, and config.
Base URL
https://api.warpflow.ai/api/v1Application endpoints in the tables are prefixed with /api/v1, except /health. Most application endpoints require Bearer authentication. Health, public documentation, /api/v1/public/*, and the location_id session flow are exceptions; custom events, assistant tools, and provider webhooks use their own credentials—see Authentication.
Interactive API docs
Explore and test the documented endpoints in the browser using the interactive API documentation:
The interactive docs (Scalar) let you authorize with the credential supported by each endpoint and make live requests against the API. Use them to explore request/response schemas, try out endpoints, and generate code snippets.
The OpenAPI spec is also available for importing into Postman, Insomnia, or code generators: Download OpenAPI spec
Credential capabilities
Published endpoints can require different credentials. Tenant API keys can access same-tenant endpoints protected by tenant scope alone, including basic contact CRUD, conversations, email send, aliases GET, and tasks. Configuration writes, Signal Guard, SMS filters, and email settings writes require manager or higher rights through Cognito ID tokens or the operator admin key. Usage reads and tenant scaffolding require admin rights; usage writes require superuser rights. Embedded session tokens have agent rights. Custom events use a source webhook key, the Telnyx assistant tool uses an assistant secret, and other provider webhooks verify provider signatures.
Endpoint groups
Webhooks—Inbound event ingestion
| Method | Path | Description |
|---|---|---|
| POST | /events/{tenant_id}/{source_id} | Custom webhook—any JSON payload. Uses a source webhook key; ?mode=sync evaluates rules only |
| POST | /webhook/zapier/{tenant_id} | Zapier pipeline—full classify → score → route → reply |
Webhook endpoints for connected integrations are configured automatically. They use provider-specific signature verification, not Bearer auth.
AI Actions—Classify, score, generate reply
| Method | Path | Description |
|---|---|---|
| POST | /actions/zapier/{tenant_id}/classify-conversation | Classify (currently unavailable: no LLM provider) |
| POST | /actions/zapier/{tenant_id}/score-contact | Score a contact with heuristic scoring |
| POST | /actions/zapier/{tenant_id}/generate-reply | Generate (currently unavailable: no LLM provider) |
Email—Send, receive, manage
| Method | Path | Description |
|---|---|---|
| POST | /tenants/{tenant_id}/email/send | Send email via connected email account |
| GET | /tenants/{tenant_id}/email/aliases | List aliases for the connected account |
| PATCH | /tenants/{tenant_id}/email/aliases | Update aliases |
| PATCH | /tenants/{tenant_id}/email/signature | Update signature |
| POST | /tenants/{tenant_id}/email/connect | Connect an email account |
| GET | /tenants/{tenant_id}/email/grants | List connected email accounts |
| DELETE | /tenants/{tenant_id}/email/grants/{grant_id} | Disconnect an email account |
| GET | /tenants/{tenant_id}/email/grants/{grant_id} | Get grant status |
| GET | /tenants/{tenant_id}/email/auth-url | Get OAuth URL for email connection |
| POST | /tenants/{tenant_id}/email/callback | Exchange OAuth code for grant |
Conversations—Multi-channel history
| Method | Path | Description |
|---|---|---|
| GET | /tenants/{tenant_id}/conversations | List conversations grouped by contact. Filter with ?alias= for email |
| GET | /tenants/{tenant_id}/conversations/{contact_id} | Full conversation history (SMS, email, voice) |
| POST | /tenants/{tenant_id}/conversations/{contact_id}/messages | Send a message (SMS or email) |
| POST | /tenants/{tenant_id}/conversations/{contact_id}/read | Mark all messages for a contact as read |
| POST | /tenants/{tenant_id}/conversations/{contact_id}/approve-draft | Approve and send a pending AI draft |
| POST | /tenants/{tenant_id}/conversations/{contact_id}/dismiss-draft | Dismiss a pending AI draft without sending |
Contacts
| Method | Path | Description |
|---|---|---|
| GET | /tenants/{tenant_id}/contacts | List contacts for a tenant |
| POST | /tenants/{tenant_id}/contacts | Create a new contact |
| GET | /tenants/{tenant_id}/contacts/{contact_id} | Get contact details |
| PATCH | /tenants/{tenant_id}/contacts/{contact_id} | Update contact (name, email, phone, notes) |
| DELETE | /tenants/{tenant_id}/contacts/{contact_id} | Soft-delete a contact (moves to archived) |
| POST | /tenants/{tenant_id}/contacts/resolve | Resolve a contact by phone/email |
| POST | /tenants/{tenant_id}/contacts/merge | Merge duplicate contacts |
| GET | /tenants/{tenant_id}/contacts/{contact_id}/detail | Full contact detail with scoring, source channel, and history |
| GET | /tenants/{tenant_id}/contacts/{contact_id}/timeline | Unified conversation timeline |
Search
| Method | Path | Description |
|---|---|---|
| POST | /tenants/{tenant_id}/search | Search contacts and conversations. Warm search (6 months) by default; add deep: true for full scan |
Leads
| Method | Path | Description |
|---|---|---|
| GET | /tenants/{tenant_id}/leads | List leads (filter by state, score tier, date range) |
| GET | /leads/{lead_id} | Get lead details with score and classification |
| GET | /leads/{lead_id}/conversation | Full conversation log for a lead |
| GET | /callbacks?tenant_id={tenant_id} | Deprecated task queue; use tenant tasks |
Sessions
| Method | Path | Description |
|---|---|---|
| POST | /sessions | Mint a short-lived, tenant-scoped session token |
Request — the direct tenant_id flow requires an admin API key or authorized Cognito token:
{ "tenant_id": "acme" }Response (200):
{
"token": "eyJzdWIiOiJhY21lIiwibG9jIjpudWxsLCJpYXQiOjE3NzE3ODA2MDAwMDAsImV4cCI6MTc3MTc4NDIwMDAwMH0.k3Jq7f8XyR2mNvB0aL9pQwZs1TcHdEfGiJkLmNoPqRs",
"tenant_id": "acme",
"expires_at": 1771784200000
}expires_at is Unix epoch milliseconds, not an ISO-8601 string. The token is two base64url segments joined by a dot — it resembles a JWT but is not one, so do not hand it to a JWT library.
The returned token is a bearer credential. Treat it like a password. Anyone holding it can act as an agent on that one tenant — reading and writing conversations, contacts and leads — until it expires. It cannot reach anything requiring manager, admin or superuser rights, and it cannot reach any other tenant. It lives for one hour from the moment it is minted, and there is no way to revoke it early: nothing is stored server-side to revoke, so a leaked token stays valid for the remainder of its hour. Mint one per session, keep it server-side or in memory, and never put it in a URL, a log, or client-side storage.
Errors: 400 BAD_REQUEST (no tenant_id, or malformed JSON), 401 UNAUTHORIZED (missing or invalid admin key), 403 FORBIDDEN (the caller has no access to that tenant), 503 SERVICE_UNAVAILABLE (session signing is not configured).
The alternative { "location_id": "YOUR_LOCATION_ID" } flow currently requires no credential; this behavior is tracked in #2237.
See Session tokens for the full walkthrough.
API Keys
| Method | Path | Description |
|---|---|---|
| GET | /tenants/{tenant_id}/api-keys | List API keys (prefix only, not full key) |
| POST | /tenants/{tenant_id}/api-keys | Create a new API key (max 3 active) |
| DELETE | /tenants/{tenant_id}/api-keys/{key_id} | Revoke an API key |
| POST | /tenants/{tenant_id}/api-keys/rotate | Rotate a key (72-hour grace period) |
Config
| Method | Path | Description |
|---|---|---|
| GET | /config/{tenant_id} | Get tenant configuration |
| PUT | /config/{tenant_id} | Replace tenant configuration |
| PATCH | /config/{tenant_id} | Partial update tenant configuration |
Webhook Subscriptions—Outbound event delivery
| Method | Path | Description |
|---|---|---|
| GET | /tenants/{tenant_id}/webhooks | List webhook subscriptions |
| POST | /tenants/{tenant_id}/webhooks | Create a webhook subscription |
| DELETE | /tenants/{tenant_id}/webhooks/{webhook_id} | Delete a webhook subscription |
| POST | /tenants/{tenant_id}/webhooks/{webhook_id}/test | Send a test event to a webhook endpoint |
Calls
| Method | Path | Description |
|---|---|---|
| GET | /tenants/{tenant_id}/calls | List call history |
| POST | /tenants/{tenant_id}/calls/outbound | Initiate an outbound call |
| POST | /tenants/{tenant_id}/calls/{call_session_id}/transfer | Transfer an active call |
| GET | /tenants/{tenant_id}/calls/{contact_id}/recording | Get a call recording |
Tasks
| Method | Path | Description |
|---|---|---|
| GET | /tenants/{tenant_id}/tasks | List task queue (status, type, limit, cursor) |
| PATCH | /tenants/{tenant_id}/tasks/{task_id} | Update task with lead_id, status/disposition/snooze |
Guardrails
| Method | Path | Description |
|---|---|---|
| GET | /guardrails/{tenant_id}/active | List active Signal Guard rules |
| GET | /guardrails/{tenant_id}/audit | Get Signal Guard audit log |
| GET | /guardrails/{tenant_id}/stats | Get Signal Guard statistics |
| PATCH | /guardrails/{tenant_id} | Update Signal Guard rules |
| GET | /guardrails/{tenant_id}/export | Export guardrail data |
Pipeline—Test & process
| Method | Path | Description |
|---|---|---|
| POST | /test/{tenant_id} | Dry-run a message through the pipeline (classify, score, route, reply). Nothing is sent. |
SMS Filters
All SMS filter operations require manager or higher rights. DELETE accepts { "phone": "+15551234567" } and removes that one phone, returning { "status": "removed" }.
| Method | Path | Description |
|---|---|---|
| GET | /tenants/{tenant_id}/sms-filters/blocklist | List blocked phone numbers |
| POST | /tenants/{tenant_id}/sms-filters/blocklist | Add a phone number to the blocklist |
| DELETE | /tenants/{tenant_id}/sms-filters/blocklist | Remove the phone supplied in the JSON body |
Billing
| Method | Path | Description |
|---|---|---|
| GET | /billing/status | Get billing status and current plan |
| POST | /billing/activate | Activate subscription (trial to paid) |
| POST | /billing/checkout | Create a checkout session for plan purchase |
| POST | /billing/portal | Get billing portal URL |
| GET | /billing/credits/balance | Get credit balance |
| GET | /billing/credits/transactions | Get credit transaction history |
Additional published operations
| Method | Path | Description |
|---|---|---|
| GET | /usage/{tenant_id} | Read tenant usage (admin or higher) |
| POST | /config/{tenant_id}/scaffold | Scaffold tenant configuration (admin or higher) |
| POST | /actions/ghl/{action_key} | GHL workflow dispatch; Bearer auth and extras location/contact required |
| POST | /actions/{action_key} | Legacy GHL workflow dispatch alias |
| GET | /config/presets | List industry presets |
| GET | /config/{tenant_id}/audit | Read tenant configuration audit entries |
| POST | /tenants/{tenant_id}/contacts/enriched | Fetch enriched contacts with request filters |
| POST | /tenants/{tenant_id}/conversations/{contact_id}/messages/subject | Label which contact a message concerns |
| POST | /tenants/{tenant_id}/leads | Create a lead |
| GET | /tenants/{tenant_id}/leads/{lead_id} | Read a tenant lead |
| PATCH | /tenants/{tenant_id}/leads/{lead_id} | Update a tenant lead |
| POST | /tenants/{tenant_id}/leads/{lead_id}/conversation | Append lead conversation entries |
| PUT | /tenants/{tenant_id}/leads/{lead_id}/conversation/import | Import lead conversation entries |
| POST | /tenants/{tenant_id}/leads/{lead_id}/score | Run scoring for a tenant lead |
| POST | /tenants/{tenant_id}/leads/{lead_id}/transition | Transition a tenant lead state |
| POST | /tenants/{tenant_id}/pipeline/batch | Batch pipeline processing; tier restrictions apply |
The unauthenticated health endpoint is GET /health, outside the /api/v1 prefix. GET /usage/{tenant_id} and POST /config/{tenant_id}/scaffold are tenant-admin reachable. PUT /usage/{tenant_id} is superuser-only and excluded; bare GET /config is dashboard-only, not role-gated, and excluded.
Common response patterns
Success
Success envelopes vary by endpoint. This illustrative action response does not apply to every route.
{
"success": true,
"data": { ... }
}Paginated list
{
"conversations": [ ... ],
"next_cursor": "next_page_token"
}This is a conversation list example. Use its next_cursor value as ?cursor=<value> for the next page.
Error
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or expired token"
}
}The HTTP status carries the status; error.code is the machine-readable value to branch on. Some errors add an error.details field with the failing fields.
See Errors for the full error code reference.
Rate limits
Limits vary by route. Email sending is limited to 50 requests per hour per tenant; public lead submissions use a separate IP-based limit. Custom events do not promise a per-tenant throttle.
The shared admin API limiter covers /config/*, /guardrails/*, /billing/*, /tenants/*, and /admin/*. Zapier pipeline and standalone Zapier actions have a webhook limiter; workflow actions use a separate limiter. Normal limiter evaluations emit X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix seconds), including on allowed requests. Store-error or skipped-key branches can omit those headers.
Some rate-limited responses include Retry-After in seconds. Use it when present; do not assume every usage-cap or throttling response supplies that header.
Pagination
Pagination varies by endpoint. Conversation lists accept cursor, use a fixed page size of 50 leads, and return next_cursor. Conversation detail currently returns next_cursor: null. Consult each endpoint schema rather than assuming every list supports limit or returns has_more.