Arraxis API
Version 1. Create and read leads, move a lead along your pipeline, add notes, and get told when something changes.
Start here
In Arraxis open Settings, then Lead sources, then Keys for other apps, and press Make a key. Copy it straight away: for your safety Arraxis shows it only once. Every call goes to this address:
https://app.arraxis.site/api/v1Check that your key works:
curl -H "Authorization: Bearer arx_live_YOUR_KEY" https://app.arraxis.site/api/v1/whoamiEverything is JSON. Field names are snake_case, times are ISO 8601 in UTC (2026-09-30T09:12:44.120Z), and an empty value is null, never a dash. Fields you send that Arraxis does not know are ignored; fields Arraxis adds to its answers later must be ignored by your code too.
Authentication
Send the key in the header Authorization: Bearer <key>. Never put it in the address: a key in the address is refused (400) because addresses end up in logs. A key can only do what was ticked when it was made:
leads:read: read leads, stages, sources and fields.leads:write: add leads, change a lead's stage, add notes. A key that cannot also read gets back only{ "id", "url" }from these calls, never a lead's details.hooks:manage: register and remove addresses that receive events. Those addresses receive each lead's full details.
A key acts on the whole workspace, money values included. Keep it secret like a password. An owner or manager can switch a key off at any time; if the person who made a key leaves the team, the key is paused until an owner turns it back on.
Errors
Every error is application/problem+json (RFC 9457): type, title, status, detail (what to fix, in plain words), code, request_id and, for 400 and 422, errors[] with the field name of each problem. Quote the request_id when you ask for help.
| HTTP | code | When |
|---|---|---|
| 400 | invalid_request | The request is not valid. The body is not JSON, is not an object, a query value is wrong, a cursor is wrong, or the key was sent in the address. |
| 401 | unauthorized | Missing or invalid API key. The Authorization header is missing, the key is not valid, was switched off, has expired or is paused. |
| 403 | forbidden | This key cannot do that. The key lacks the permission for this call, or the workspace is not active. |
| 404 | not_found | Not found. The id does not exist, or belongs to another workspace (never 403: ids cannot be probed). |
| 409 | idempotency_conflict | Idempotency-Key reused with a different request. The same Idempotency-Key was sent with a different method, path or body. |
| 409 | idempotency_in_progress | The first request with this Idempotency-Key is still running. Retry after the Retry-After seconds. |
| 409 | conflict | Conflict. The lead changed at the same moment, a booked time was just taken, the source is paused, or a limit was reached. |
| 409 | stage_not_allowed | This stage move is not allowed. The pipeline forbids the move. Send override: true to force it. |
| 413 | payload_too_large | Request body too large. The body is over 64 KB. |
| 415 | unsupported_media_type | Send JSON. A write was sent without Content-Type: application/json. |
| 422 | validation_failed | Some fields are not valid. One or more fields are wrong; errors[] lists each with the field name. |
| 429 | rate_limited | Too many requests. The per-key limit (120 requests a minute by default) or the bad-key limit was reached. Wait Retry-After seconds. |
| 500 | internal_error | Something went wrong. An unexpected error. Quote request_id when you contact support. |
Idempotency
Every POST accepts an Idempotency-Key header (1 to 200 visible characters). Send the same key with the same body and you get the first answer back, with the header Idempotency-Replayed: true, for 24 hours. The same key with a different body is a 409. Use something unique per event, such as your entry id. Creating a lead also has its own protection: send external_id (unique per source) and a repeat returns the same lead.
Pagination
Lists are newest first. limit is 1 to 100 (25 by default). The answer is { "data": [...], "has_more": true, "next_cursor": "..." }; pass next_cursor as cursor to get the next page, with the same filters. Pages never skip or repeat a lead that did not change. To poll for changes use updated_since.
Rate limits
A key may make 120 requests a minute. Every answer carries X-RateLimit-Limit and X-RateLimit-Remaining; over the limit you get 429 with Retry-After (seconds). The API sends no CORS headers: call it from a server, never from a browser page. Changes inside version 1 only ever add things.
Endpoints
GET/api/v1/whoami
Who this key is · key needs any
Any valid key. Use it as the connection test.
Possible errors: 401, 403, 429.
GET/api/v1/leads
List leads, newest first · key needs leads:read
sort | query | received_at (default) or updated_at; always newest first. |
stage | query | Comma list of stage keys (at most 20). |
source | query | Comma list of source keys (at most 20); meta also matches each Meta form. |
owner_email | query | One member's email; none = unassigned. |
q | query | Name, email or phone contains (at most 100 characters). |
phone | query | Exact phone, normalised with the workspace's region. |
email | query | Exact email, any case. |
received_since | query | ISO instant, inclusive. |
received_before | query | ISO instant, exclusive. |
updated_since | query | ISO instant: the polling filter for Zapier and Make. |
Possible errors: 400, 401, 403, 429.
POST/api/v1/leads
Create a lead · key needs leads:write
Goes through the same intake core as every other source: duplicates, attribution, assignment and notifications work the same. A lead with the same phone as an open lead becomes a re-enquiry (outcome merged).
Example body:
{
"name": "Alex Morgan",
"phone": "+1 555 0100",
"email": "alex.morgan@example.com",
"service": "Consultation",
"location": "Downtown",
"source": "api",
"external_id": "typeform-8812",
"attribution": {
"utm_source": "facebook"
}
}Answer 201:
{
"outcome": "created",
"lead": {
"id": "6f1c2d0e-8a41-4b7e-9c55-3e2f1a9b7c10",
"url": "https://app.arraxis.site/leads/6f1c2d0e-8a41-4b7e-9c55-3e2f1a9b7c10",
"created_at": "2026-09-30T09:12:44.120Z",
"updated_at": "2026-09-30T09:12:44.120Z",
"received_at": "2026-09-30T09:12:44.120Z",
"name": "Alex Morgan",
"phone": "+15550100",
"phone_raw": "+1 555 0100",
"email": "alex.morgan@example.com",
"city": null,
"gender": null,
"age": null,
"stage": {
"key": "new",
"name": "New",
"kind": "new"
},
"source": {
"id": "a41f0b7c-0000-4000-8000-000000000001",
"key": "api",
"name": "API",
"kind": "api"
},
"owner": null,
"service_interest": "Consultation",
"service": null,
"location": "Downtown",
"tags": [],
"custom": {
"preferred_time": "Morning"
},
"value": null,
"booked_at": null,
"landed_at": null,
"won_at": null,
"lost_at": null,
"next_action_at": "2026-09-30T09:12:44.120Z",
"external_id": "typeform-8812",
"attribution": {
"utm_source": "facebook",
"utm_campaign": "autumn-offer"
},
"answers": {},
"ads": {
"platform": null,
"campaign_id": null,
"campaign_name": null,
"adset_id": null,
"ad_id": null,
"form_id": null
},
"duplicate_of": null
}
}Possible errors: 400, 401, 403, 409, 413, 415, 422, 429.
GET/api/v1/leads/{id}
One lead · key needs leads:read
A merged lead answers with its survivor and the header Arraxis-Merged-Into.
id | path, required | The lead's id. |
Answer 200:
{
"id": "6f1c2d0e-8a41-4b7e-9c55-3e2f1a9b7c10",
"url": "https://app.arraxis.site/leads/6f1c2d0e-8a41-4b7e-9c55-3e2f1a9b7c10",
"created_at": "2026-09-30T09:12:44.120Z",
"updated_at": "2026-09-30T09:12:44.120Z",
"received_at": "2026-09-30T09:12:44.120Z",
"name": "Alex Morgan",
"phone": "+15550100",
"phone_raw": "+1 555 0100",
"email": "alex.morgan@example.com",
"city": null,
"gender": null,
"age": null,
"stage": {
"key": "new",
"name": "New",
"kind": "new"
},
"source": {
"id": "a41f0b7c-0000-4000-8000-000000000001",
"key": "api",
"name": "API",
"kind": "api"
},
"owner": null,
"service_interest": "Consultation",
"service": null,
"location": "Downtown",
"tags": [],
"custom": {
"preferred_time": "Morning"
},
"value": null,
"booked_at": null,
"landed_at": null,
"won_at": null,
"lost_at": null,
"next_action_at": "2026-09-30T09:12:44.120Z",
"external_id": "typeform-8812",
"attribution": {
"utm_source": "facebook",
"utm_campaign": "autumn-offer"
},
"answers": {},
"ads": {
"platform": null,
"campaign_id": null,
"campaign_name": null,
"adset_id": null,
"ad_id": null,
"form_id": null
},
"duplicate_of": null
}Possible errors: 401, 403, 404, 429.
POST/api/v1/leads/{id}/stage
Move a lead to a stage · key needs leads:write
The stage's requires (see GET /stages) decide which of date, time, amount, reason and service are needed. Sending the current stage again is a no-op. A key without leads:read gets ApiLeadRef back.
id | path, required | The lead's id. |
Example body:
{
"stage": "booked",
"date": "2026-10-02",
"time": "11:00",
"service": "Consultation"
}Possible errors: 400, 401, 403, 404, 409, 413, 415, 422, 429.
POST/api/v1/leads/{id}/notes
Add a note to a lead · key needs leads:write
id | path, required | The lead's id. |
Example body:
{
"text": "Called back, will decide on Friday."
}Possible errors: 400, 401, 403, 404, 413, 415, 422, 429.
GET/api/v1/stages
The stages of the default pipeline, in order · key needs leads:read
Possible errors: 401, 403, 429.
GET/api/v1/sources
The lead sources of the workspace · key needs leads:read
Use a key as the source of POST /leads.
Possible errors: 401, 403, 429.
GET/api/v1/fields
The custom lead fields · key needs leads:read
Keys of the custom object of POST /leads.
Possible errors: 401, 403, 429.
GET/api/v1/hooks
The addresses this key registered for events · key needs hooks:manage
Possible errors: 401, 403, 429.
POST/api/v1/hooks
Register an address for events (REST hook subscribe) · key needs hooks:manage
The signing secret is returned once.
Example body:
{
"url": "https://hooks.zapier.com/hooks/standard/123/abc/",
"events": [
"lead.created"
]
}Possible errors: 400, 401, 403, 409, 413, 415, 422, 429.
DELETE/api/v1/hooks/{id}
Unregister an address (REST hook unsubscribe) · key needs hooks:manage
Only hooks this key created; asking again answers 404.
id | path, required | The lead's id. |
Possible errors: 401, 403, 404, 429.
GET/api/v1/hooks/sample
Example events for a type · key needs hooks:manage or leads:read
An array (not a list object) of up to 3 events of exactly the delivered shape, for an app's editor.
event | query, required | An event type, e.g. lead.created. |
Possible errors: 400, 401, 403, 429.
GET/api/v1/openapi.json
This document · key needs none
No key needed.
Possible errors: none.
curl -X POST https://app.arraxis.site/api/v1/leads \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -H "Idempotency-Key: typeform-8812" \
-d '{"name":"Alex Morgan","phone":"+1 555 0100","service":"Consultation","location":"Downtown","source":"api","external_id":"typeform-8812"}'Webhooks
Arraxis can send each new lead, and other changes, to an address you register: paste it in Settings, Lead sources, Send leads to other apps (press Send a test to see one arrive), or register it through the API (POST /hooks). Each message is an HTTPS POST of JSON. Answer with any 2xx. Anything else is retried 8 times over about 31 hours (1 minute, 5 minutes, 30 minutes, 2, 5, 10 and 14 hours); a 410 answer switches the address off at once, and an address that has failed for 3 days is switched off and your owners are told. Redirects are never followed. Messages older than 7 days cannot be sent again.
lead.created | New lead |
lead.updated | Details changed |
lead.stage_changed | Stage changed |
lead.assigned | Assigned |
lead.note_added | Note added |
lead.reinquiry | Enquired again |
lead.merged | Leads merged |
appointment.booked | Appointment booked |
appointment.rescheduled | Appointment moved |
appointment.cancelled | Appointment cancelled |
appointment.completed | Appointment completed |
appointment.no_show | Did not show up |
appointment.confirmed | Appointment confirmed |
ping | Test |
The body is { id, type, timestamp, workspace_id, actor, data }. id is the same for every address and every retry: dedupe on it (or on webhook-id). data.lead is the full lead as it is when the message is built; data.change holds the event's own facts (for a stage change, from and to stage keys). actor says who caused it: a Zap that writes back to Arraxis can ignore events whose actor.kind is api_key to avoid a loop.
{
"id": "evt_1c7e2d4a-3f1b-4a51-9a0c-2b9d8e7f6a51",
"type": "lead.stage_changed",
"timestamp": "2026-09-30T10:04:11.502Z",
"workspace_id": "0b1e5c3a-…",
"actor": {
"kind": "member"
},
"data": {
"lead": {
"id": "6f1c2d0e-…",
"name": "Alex Morgan",
"phone": "+15550100",
"stage": {
"key": "booked",
"name": "Booked",
"kind": "booked"
},
"…": "the full lead"
},
"appointment": null,
"note": null,
"change": {
"from": "new",
"to": "booked",
"amount": null,
"reason": null
}
}
}Messages follow the Standard Webhooks signing scheme. Each carries three headers: webhook-id (msg_…, the same on every retry), webhook-timestamp (unix seconds of this attempt) and webhook-signature (v1,<base64>; two values separated by a space while you are replacing a secret). The signature is an HMAC-SHA256 of id.timestamp.body with your signing secret (whsec_…, base64-decoded). Verify it against the raw body, refuse timestamps more than 5 minutes old, and answer 2xx only after that:
// Node (no library)
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyArraxis(secret, headers, rawBody) {
const id = headers["webhook-id"], ts = headers["webhook-timestamp"], sigs = String(headers["webhook-signature"] || "");
if (!id || !ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const want = Buffer.from("v1," + createHmac("sha256", Buffer.from(secret.replace(/^whsec_/, ""), "base64")).update(`${id}.${ts}.${rawBody}`).digest("base64"));
return sigs.split(" ").some((s) => { const got = Buffer.from(s); return got.length === want.length && timingSafeEqual(got, want); });
}// PHP
function arraxis_verify(string $secret, array $h, string $raw): bool {
$id = $h['webhook-id'] ?? ''; $ts = $h['webhook-timestamp'] ?? ''; $sigs = $h['webhook-signature'] ?? '';
if ($id === '' || abs(time() - (int)$ts) > 300) return false;
$key = base64_decode(preg_replace('/^whsec_/', '', $secret));
$want = 'v1,' . base64_encode(hash_hmac('sha256', "$id.$ts.$raw", $key, true));
foreach (explode(' ', $sigs) as $s) if (hash_equals($want, $s)) return true;
return false;
}Zapier's "Catch Hook" and Make's "Custom webhook" do not check signatures, so for them the unguessable address is the credential: keep it private.
Zapier
Leads into Arraxis: make a key that can add leads. In your Zap use Webhooks by Zapier, POST to https://app.arraxis.site/api/v1/leads with payload type JSON; data name, phone, email, service and external_id (the trigger's entry id); headers Authorization: Bearer arx_live_… and Idempotency-Key (the entry id). Give each app its own source, or prefix the id with the app name, because external_id is unique per source. The test step sends a real lead: delete it afterwards or test with your own number.
Leads out of Arraxis: in Zapier start with Webhooks by Zapier, Catch Hook and copy its address. In Arraxis paste it under Send leads to other apps, tick New lead, save and press Send a test; then press Test trigger in Zapier to see the sample lead's fields.
A private Zapier integration can subscribe for you: POST /hooks with { "url": "{{bundle.targetUrl}}", "events": ["lead.created"] } to subscribe, DELETE /hooks/{id} to unsubscribe, and GET /hooks/sample?event=lead.created for the editor's sample data. The key needs all three permissions.
Make
Into Arraxis: use HTTP, Make a request: URL https://app.arraxis.site/api/v1/leads, method POST, headers Authorization and Idempotency-Key, body type Raw, content type JSON, and Parse response: Yes. A 422 answer names the field that is wrong in errors[].field.
Out of Arraxis: use Webhooks, Custom webhook, copy its address, paste it under Send leads to other apps, press Redetermine data structure in Make and Send a test in Arraxis. Make turns off a webhook that is not attached to a scenario for 5 days and then answers 410; Arraxis then switches the address off.
WordPress plugin
Arraxis Leads sends your website's form submissions (Contact Form 7, WPForms, Gravity Forms, Elementor, Fluent Forms, Forminator) to Arraxis, with the page they were sent from and the ad click ids of the visit. Download it from Settings, Lead sources, add a WordPress website source, upload the zip in wp-admin under Plugins, Add New, Upload Plugin, then paste the key shown in Arraxis under Settings, Arraxis Leads. Nothing is sent until you connect a key and tick a form. The plugin is also at https://app.arraxis.site/wp-plugin/arraxis-leads/arraxis-leads.zip.
The same reference in machine-readable form: openapi.json (OpenAPI 3.1).