https://api.axiums.ai/v1The AXIUMS API gives an agency programmatic access to the carrier data AXIUMS collects on behalf of its agents — returned as the carrier stated it, with sync freshness attached so you always know how current the data is.
Pending requirements are the first capability available in v1. Further capabilities are added to v1 as additive changes — new endpoints, new fields, new carriers — without a version bump, so build against it expecting it to grow.
Every response tells you not just what the carrier reported, but how reliable that answer is. A carrier we could not read is reported as such rather than returned as an empty list.
curl https://api.axiums.ai/v1/requirements?npn=8241905573 \ -H "Authorization: Bearer axm_live_7f3c9a12e8b4d6f0a5c1"
const res = await fetch( "https://api.axiums.ai/v1/requirements?npn=8241905573", { headers: { Authorization: `Bearer ${process.env.AXIUMS_API_KEY}` } } ); const data = await res.json();
import os, requests res = requests.get( "https://api.axiums.ai/v1/requirements", params={"npn": "8241905573"}, headers={"Authorization": f"Bearer {os.environ['AXIUMS_API_KEY']}"}, ) data = res.json()
API access is granted per agency and approved by AXIUMS.
Submit a request below with your agency name, primary contact, technical contact, and a short description of the integration. AXIUMS reviews it and notifies you by email at the address on the request. On approval you'll receive a one-time link to retrieve your key.
Keys are shown once and cannot be retrieved afterward. Store it in your secrets manager immediately. An agency may hold up to two active keys at a time, so a key can be rotated without downtime.
A key is scoped to your agency. It returns requirements only for agents who belong to your agency in AXIUMS and have not restricted requirements sharing. A request for an NPN outside your agency — or for an agent who has turned sharing off — returns the same response as an NPN that doesn't exist: an empty result, never an error. The API does not reveal whether an NPN it won't serve is real, nor whether an agent has opted out.
Agents can see on their own AXIUMS dashboard that their agency holds API access, and can restrict sharing at any time from their settings — a restricted agent simply returns no data. Requirements and account changes are separate switches: an agent can turn one off and leave the other on. Nothing is shared invisibly, and nothing is shared that the agent has turned off.
axm_live_7f3c9a12e8b4d6f0a5c1 # Never commit this. Never paste it into # a support ticket, chat, or email.
Pass your key as a bearer token on every request. All requests must use HTTPS.
Use api.axiums.ai. It serves the API directly. Calling axiums.ai/api/v1 redirects to another host, and most HTTP clients drop the Authorization header across a redirect, which produces a confusing 401.
A missing, malformed, revoked or expired key returns 401 with code unauthorized. Revocation takes effect immediately.
-H "Authorization: Bearer axm_live_7f3c9a12e8b4d6f0a5c1"
headers: { Authorization: `Bearer ${process.env.AXIUMS_API_KEY}` }
headers = { "Authorization": f"Bearer {os.environ['AXIUMS_API_KEY']}" }
{ "error": "unauthorized", "message": "API key is missing, malformed, revoked or expired." }
Requirements are only available for carriers AXIUMS has built extraction for. Every carrier appears in every response, including unavailable ones — absence of coverage is stated, never hidden.
| Carrier | Coverage | Notes |
|---|---|---|
| Transamerica | full | Requirement items with instructions and comments |
| Mutual of Omaha | full | Outstanding case requirements with requested dates |
| Foresters | full | Pending-issue requirements |
| Liberty Bankers | status_only | Returns an initialPremiumNotPaid flag per policy, no itemized requirements |
| Ethos | unavailable | No requirements extraction built |
| Corebridge | unavailable | No requirements extraction built |
| American Home Life | unavailable | No requirements extraction built |
Coverage expands over time. Branch on the coverage value rather than hardcoding which carriers return requirements.
{ "carrier": "ethos", "coverage": "unavailable", "syncStatus": "unavailable", "lastSyncedAt": null, "fieldProvenance": {}, "requirements": [] }
AXIUMS reads carrier portals on a daily schedule. Every carrier block carries lastSyncedAt (the last sync attempt), lastSuccessfulSyncAt (the last sync that actually succeeded, or null), and a syncStatus. Each requirement also carries its own lastSeenAt — the last sync that observed that specific requirement — so you can tell a fresh “still outstanding” from a row that hasn't been re-confirmed lately.
| Value | Meaning |
|---|---|
| ok | Last sync succeeded within 48 hours |
| stale | Last success was more than 48 hours ago |
| failing | The most recent sync attempt did not succeed, or the credential needs reauthorization |
| not_connected | The agent has not connected this carrier |
| unavailable | AXIUMS has no requirements extraction for this carrier |
This is the single most likely way to misread this API. A healthy carrier with genuinely nothing outstanding returns ok with an empty requirements array. A carrier we could not read also returns an empty array — but with failing.
Those two look identical if you only count rows, and they mean opposite things. Treat failing and stale as “do not act on this without confirming at the carrier.”
// Nothing outstanding — safe to act on { "syncStatus": "ok", "requirements": [] } // We could not read the carrier — unknown { "syncStatus": "failing", "requirements": [] }
if (c.syncStatus === "ok" && c.requirements.length === 0) { // genuinely clear } else if (["failing", "stale"].includes(c.syncStatus)) { // unknown — confirm at the carrier }
if c["syncStatus"] == "ok" and not c["requirements"]: # genuinely clear pass elif c["syncStatus"] in ("failing", "stale"): # unknown — confirm at the carrier pass
Every carrier block includes a fieldProvenance map marking where each field's value came from.
carrier_verbatim — the value exactly as the carrier reported it. Not reworded, reformatted, summarized or interpreted.
axiums_derived — derived or added by AXIUMS.
Field names are AXIUMS-normalized so carriers can be consumed uniformly, but any value marked carrier_verbatim is the carrier's own.
Carriers return different fields, so the shape of a requirement object varies by carrier. Read fieldProvenance to see what that carrier's block contains.
"fieldProvenance": { "policyNumber": "carrier_verbatim", "policyStatus": "carrier_verbatim", "requirement": "carrier_verbatim", "comments": "carrier_verbatim", "requestedDate": "carrier_verbatim", "insuredName": "axiums_derived" }
Returns every carrier block for a single agent, identified by National Producer Number.
The agent's National Producer Number. Must be 4–12 digits.
Restrict to one carrier: transamerica, mutual-of-omaha, foresters, liberty-bankers.
Either open (default) or all. open excludes requirements the carrier has marked resolved — cancelled, completed, received, waived or withdrawn. It deliberately keeps anything still in flight, including items marked submitted, since a submitted requirement has not been accepted yet. Carrier status strings are always returned verbatim, so you can filter further yourself.
agent.name is string | null — an agent may not have set their name, so it can be present and null. Individual requirement fields can also be null where the carrier didn't supply a value. Parse defensively. agencyId is not returned: your key is the agency.
curl "https://api.axiums.ai/v1/requirements?npn=8241905573" \ -H "Authorization: Bearer $AXIUMS_API_KEY"
const url = new URL("https://api.axiums.ai/v1/requirements"); url.searchParams.set("npn", "8241905573"); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.AXIUMS_API_KEY}` } }); const { agent, carriers } = await res.json();
res = requests.get( "https://api.axiums.ai/v1/requirements", params={"npn": "8241905573"}, headers={"Authorization": f"Bearer {API_KEY}"}, ) res.raise_for_status() payload = res.json()
{ "agent": { "npn": "8241905573", "name": "Jordan Ellery" }, "carriers": [ { "carrier": "mutual-of-omaha", "coverage": "full", "syncStatus": "ok", "lastSyncedAt": "2026-08-09T15:48:56.520Z", "fieldProvenance": { /* … */ }, "requirements": [ { "policyNumber": "BU6747044", "policyStatus": "Issued", "requirement": "GOOD HEALTH STATEMENT - SIMPLIFIED", "comments": "", "requestedDate": "2026-07-29", "insuredName": "MARK HOPKINS" } ] }, { "carrier": "liberty-bankers", "coverage": "status_only", "syncStatus": "ok", "lastSyncedAt": "2026-08-09T15:44:59.614Z", "requirements": [ { "policyNumber": "42946S", "initialPremiumNotPaid": true } ] } ] }
Every requested NPN appears in the response with an explicit status. A batch never silently drops an NPN.
Array of NPNs. Maximum 100 per request.
Restrict all agents to one carrier.
open (default) or all.
A found result carries an agent object and carriers. An unavailable result carries only npn and status — no agent object, because no agent was resolved.
Per-NPN failures do not fail the batch. The HTTP status is 200 when the request itself was valid, even if every NPN in it was unavailable.
curl -X POST https://api.axiums.ai/v1/requirements/batch \ -H "Authorization: Bearer $AXIUMS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "npns": ["8241905573", "6017384492"], "carrier": "foresters" }'
const res = await fetch("https://api.axiums.ai/v1/requirements/batch", { method: "POST", headers: { Authorization: `Bearer ${process.env.AXIUMS_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ npns: ["8241905573", "6017384492"], carrier: "foresters" }) });
res = requests.post( "https://api.axiums.ai/v1/requirements/batch", headers={"Authorization": f"Bearer {API_KEY}"}, json={ "npns": ["8241905573", "6017384492"], "carrier": "foresters", }, )
{ "requested": 2, "summary": { "found": 1, "unavailable": 1 }, "results": [ { "agent": { "npn": "8241905573", "name": "Jordan Ellery" }, "status": "found", "carriers": [ /* … */ ] }, { "npn": "6017384492", "status": "unavailable" } ] }
An append-only change ledger for one agent's requirements: every time a requirement appears, changes, resolves, or reappears, an event is recorded. Poll this feed on your own schedule with a cursor to receive only what is new since you last read.
The agent's NPN, within your agency.
Opaque cursor from a previous response's next_cursor. Omit to read from the beginning.
Page size, 1–500 (default 100).
The cursor is opaque and stable — treat it as a token, do not parse it. Events are totally ordered (by time, then id), so a cursor names an exact position in the ledger. next_cursor is always present; an empty page returns the same cursor you sent. The ledger is append-only, so replaying from an old cursor is supported — you will re-receive every event after it, in order.
| type | meaning |
|---|---|
APPEARED | A new requirement was captured. |
CHANGED | A field changed; diff carries the old→new values (e.g. a comment or deadline edit). |
RESOLVED | The requirement is no longer on the carrier. |
REAPPEARED | A previously resolved requirement returned. |
Each entry has an event (id, type, at, humanized, raw diff) and its item (carrier, carrierLabel, policyNumber, requirementKey, insuredName, description, status, state, details, and the lifecycle timestamps firstSeenAt / lastSeenAt / resolvedAt). You receive exactly what the agent sees on their own dashboard — their own policies only.
The response includes a carriers array — one entry per carrier with lastSuccessfulSyncAt (the last time we successfully synced that carrier for this agent). Together with each item's lastSeenAt (the last sync that observed the requirement), this lets you tell “no changes” (a recent successful sync with an empty page) from “not recently checked” (a stale or missing sync). The same lastSuccessfulSyncAt appears per carrier on Get requirements, and each requirement there carries its own lastSeenAt.
An agent can restrict requirements sharing from their AXIUMS settings. A restricted agent — like an NPN outside your agency — returns an empty feed (HTTP 200), never an error, so opt-outs cannot be enumerated. Rate limits are shared with the other v1 endpoints.
Store the last next_cursor. On each poll, request ?npn=…&since=<stored cursor>, process the returned events, and persist the new next_cursor. Events are append-only and replayable, so a crash mid-batch is safe — re-poll from the last cursor you durably stored.
curl "https://api.axiums.ai/v1/requirements/updates?npn=8241905573&since=CURSOR" \ -H "Authorization: Bearer sk_live_…"
{
"updates": [
{
"event": { "id": "re_…", "type": "CHANGED", "at": "2026-09-03T04:02:27Z",
"humanized": "Changed 2026-09-03: deadline FILE 07/13 → FILE 07/20",
"diff": { "deadline": { "old": "FILE 07/13", "new": "FILE 07/20" } } },
"item": { "carrier": "mutual-of-omaha", "policyNumber": "BU6683528",
"insuredName": "GERARDO CEBALLOS", "status": "Pending", "state": "OPEN",
"details": { "deadline": "FILE 07/20" } }
}
],
"next_cursor": "MTc4ODQw…"
}An append-only ledger of what changed in an agent's book — policies placed, policies lapsed, chargebacks posted, requirements appearing and resolving, premium and commission edits, downline activity. Poll it with a cursor, exactly like the requirements updates feed.
This is movement, not state. For what an agent currently holds, use Get requirements.
This is the only v1 endpoint that accepts either credential.
| Credential | Scope | npn |
|---|---|---|
Agency key (axm_live_…) | The named agent, within your agency | Required |
Agent token (axm_agent_…) | That agent only | Ignored |
An agent token already identifies its agent. npn is ignored on that path rather than rejected: honouring it would let a token widen its own reach, and erroring would imply the parameter means something there.
The agent's NPN. Required with an agency key, ignored with an agent token.
Opaque cursor from a previous response's next_cursor. Omit to read from the beginning.
Page size, 1–500 (default 100).
Comma-separated categories, e.g. LAPSE_DETECTED,CHARGEBACK_POSTED.
Carrier slug, e.g. mutual-of-omaha.
ISO date — only events at or after it.
ISO date — only events at or before it.
own, downline, or all (default).
Same contract as the requirements feed: opaque, stable, ordered by time then id, and next_cursor is always present — an empty page returns the cursor you sent. The cursor advances only to an event that was actually returned, never to one filtered out along the way, so nothing is skipped when new events arrive mid-poll.
Each event carries id, category, carrier, carrierLabel, policyNumber, insuredName, field, oldValue, newValue, at, syncLogId and scope.
scope: "own" is a policy the agent holds. scope: "downline" is the agent's own record of a downline producer's policy, derived from the agent's own carrier captures — never a read of another account. A downline event carries no insuredName: that insured is not your caller's client.
Historical backfill records are omitted. They carry the date AXIUMS first recorded a policy, not the date anything happened — publishing them as dated changes would report years of placements on a single day. A NEW_POLICY whose policy was placed outside a 45-day window either side of now is omitted for the same reason.
An agent can restrict the change feed independently of requirements sharing — the two are separate switches, because a change ledger discloses movement over time rather than a list of what is outstanding. A restricted, unknown, or cross-agency agent returns an empty page (HTTP 200) with your cursor echoed back: the same shape as “nothing changed”, so opt-outs cannot be enumerated.
Authentication is checked before input validation. An unauthenticated request with a malformed since returns 401, not 400 — a caller who has not authenticated is told nothing about which parameters this endpoint parses.
curl "https://api.axiums.ai/v1/changes?npn=8241905573&limit=50" \ -H "Authorization: Bearer axm_live_…"
curl "https://api.axiums.ai/v1/changes?scope=own" \ -H "Authorization: Bearer axm_agent_…"
{
"changes": [
{
"id": "ace_…",
"category": "LAPSE_DETECTED",
"carrier": "mutual-of-omaha",
"carrierLabel": "Mutual of Omaha",
"policyNumber": "BU6347993",
"insuredName": "MARCUS WEBB",
"field": "status",
"oldValue": "IN_FORCE",
"newValue": "LAPSED",
"at": "2026-09-06T15:43:02Z",
"syncLogId": "csl_…",
"scope": "own"
}
],
"next_cursor": "MTc4ODc3…"
}The agent's book, newest placement first, cursor-paged. Summary fields only — carrier, insured, product, status, premium, face, advance, backend and the three dates. For everything else, follow with the detail endpoint below.
| Parameter | Meaning |
|---|---|
npn | Agency keys only, and required for them: names the one in-agency agent to read. Ignored on an agent token, which is already scoped to one agent. |
cursor | Opaque; pass back the previous response's next_cursor. A null cursor means the last page. |
limit | 1–200, default 50. |
carrier | Carrier slug, e.g. mutual-of-omaha. |
status | Policy status, e.g. IN_FORCE, LAPSED. |
placed_from / placed_to | ISO dates bounding placedDate. |
Everything AXIUMS holds on one policy: the insured's identity and their contact details, product and face, status with the carrier's own wording, application / issue / placed dates, premium and annualised premium, the expected commission alongside what the carrier has actually paid net of chargebacks, advance and backend progress, chargeback state, every open requirement from the canonical ledger, and the full change history of what moved and when.
Contact is returned from two sources, both labelled, neither silently preferred. contact.policy is the denormalised phone and email captured when the policy was projected; contact.carrierFeed is read live from that carrier's own capture and is the only source of a full address. They can disagree, and which one is right depends on which synced more recently — so the answer shows both rather than picking for you. Carriers differ in what they provide at all: Mutual of Omaha gives phone, email and address; Transamerica gives the owner's phone and address but no email; the rest give a name only, with a note saying so.
Consent is separate. Both endpoints honour the agent's Policy details via API switch, distinct from the requirements and changes switches precisely because this one discloses a client's phone number and home address. A restricted agent, an unknown NPN and a cross-agency NPN are indistinguishable: the list returns an empty page and the detail returns the same 404 body as a policy number that exists nowhere. That sameness is deliberate — any difference would let a caller enumerate whose policies exist, or who opted out.
curl -H "Authorization: Bearer axm_agent_…" \ "https://www.axiums.ai/api/v1/policies?carrier=mutual-of-omaha&limit=50"
curl -H "Authorization: Bearer axm_agent_…" \ "https://www.axiums.ai/api/v1/policies/mutual-of-omaha/BU6347993"
{
"found": false,
"message": "No policy with that number in your book."
}AXIUMS runs a Model Context Protocol server so an agent can point their own AI client — Claude, ChatGPT, or anything that speaks MCP — at their own book. This is the agent's personal surface, not an agency integration: it authenticates as one agent and returns only that agent's data.
Transport is Streamable HTTP in stateless mode. GET and DELETE answer 405 by spec — there is no standalone stream to open.
| Tool | Returns |
|---|---|
get_my_requirements | Outstanding carrier requirements, with the per-carrier map of which contact fields that carrier actually provides. |
get_my_book | Up to 150 most recently placed policies: insured, product, carrier, status, annualised premium, advance and backend. |
get_my_chargebacks | Own advance clawbacks and the override clawbacks the agent carries. |
get_my_comp_structure | Comp level, advance/backend split, per-carrier chargeback schedules, true comp rate per product. |
get_policy_details | Everything about ONE policy: insured identity and client contact details (phone, email, home address), product, status, dates, premium, expected vs actually-paid commission net of chargebacks, advance/backend progress, chargeback state, open requirements, and full change history. Takes carrier + policyNumber. |
get_my_changes | What changed recently, plus the agent's current daily brief text when one exists. |
get_my_changes is the only tool that takes arguments: sinceDays (1–90, default 2), categories, and scope (own / downline / all). They only ever narrow a result. No tool takes an agent identifier — the agent is bound from the verified credential when the server is constructed, so there is nothing a prompt can pass that changes whose data comes back.
One further tool — a downline view — is built but not enabled: it discloses other AGENTS' names and comp levels, and those people did not choose to have them sent to a model provider. It is absent from tools/list rather than present and erroring. The per-policy contact lookup that used to sit alongside it now ships inside get_policy_details, gated on the agent's own policy-sharing switch.
get_policy_details honours its own per-agent switch (Policy details via API in AXIUMS Settings), separate from the requirements and changes switches because it discloses the client's contact information. With it off the tool is absent from tools/list.
get_my_changes honours the same change-sharing switch as the feed above. With sharing off the tool is absent from tools/list entirely — a disabled tool that still appeared would be called, and the error explained back to the agent as if it were a finding.
OAuth 2.1 is the normal path, and it needs no configuration beyond the server address: the client discovers everything else and the agent signs in. Add https://www.axiums.ai/api/mcp as a custom connector, sign in, authorise. Verified end to end with claude.ai and Claude Desktop, and with ChatGPT — which requires Developer mode (Settings → Security and login), available on Plus, Pro, Business, Enterprise and Edu but not Free.
The alternative is a personal token (axm_agent_…) generated on the Connectors page and passed as Authorization: Bearer. That is for clients that cannot do OAuth — Claude Code today.
Discovery follows the specs: /.well-known/oauth-protected-resource (RFC 9728) advertises the resource and its authorisation server, and /.well-known/oauth-authorization-server (RFC 8414) advertises the endpoints. PKCE with S256 is required. A client may identify itself either by client ID metadata document (a URL client_id we fetch and validate, which is what claude.ai uses) or by dynamic client registration (RFC 7591 at /api/oauth/register, which is what ChatGPT uses); both are advertised and both work. A 401 from the MCP endpoint carries a WWW-Authenticate header naming the resource metadata document, so a client can complete the handshake without being configured by hand.
Tokens are validated against this server's own resource URI — a token minted for a different audience does not authenticate here.
https://www.axiums.ai/api/mcp
claude mcp add --transport http axiums \ https://www.axiums.ai/api/mcp \ --header "Authorization: Bearer axm_agent_…"
curl https://www.axiums.ai/.well-known/oauth-protected-resource curl https://www.axiums.ai/.well-known/oauth-authorization-server
Marketing partners send an agent's Facebook lead-ad submissions to AXIUMS with a Zap. This is the write side of the platform and does not use an axm_live_ API key — each authorized agent gets their own webhook URL and their own secret.
Both values are issued in the partner dashboard, per client, and can be re-read there at any time. Generating a new one replaces that client's value and only that client's — sibling Zaps keep working.
POST https://axiums.ai/api/marketing/hooks/lead/<token> with Content-Type: application/json.
Two independent factors, and both are required: the 256-bit <token> in the path, and the client's secret in an x-axiums-secret header. The token alone does not authenticate — a request with no header is rejected exactly like one with a wrong header.
A lead is accepted only while the agent has authorized the partner. The agent can revoke at any time, and a revoked link stops ingesting immediately; a partner cannot re-authorize itself.
curl -X POST https://axiums.ai/api/marketing/hooks/lead/<token> \ -H "x-axiums-secret: <secret>" \ -H "Content-Type: application/json" \ -d '{"leadgen_id":"1029384756","created_time":"2026-09-04T14:02:11Z","first_name":"Dana","last_name":"Reyes","email":"dana@example.com","state":"AZ"}'
{ "ok": true, "deduped": false, "leadId": "cmt..." }
Map your Zap's Data fields to any of the accepted keys below. Every field accepts several spellings, so a Facebook Lead Ads payload usually maps straight through.
Email or phone is required — at least one of the two, not both. State is recommended, not optional: a lead with no state cannot be placed on the close-rate map, so a Zap that omits it produces an empty map that looks broken.
| Field | Map to any of | Needed |
|---|---|---|
| Lead ID | leadgen_id · leadgenId · id · lead_id · contact_id · contactIdThe dedup key — the same lead never lands twice. In GoHighLevel this is the CONTACT id, which dedups per person rather than per Meta lead; map the Meta lead id into a custom field to keep per-lead dedup. | required |
| Created time | created_time · created_at · createdTime · created · date_created · dateCreatedWhen the consumer submitted. Anchors the match window. | required |
| Name | first_name + last_name · full_name · nameEither the two parts or the full name. | required |
email · email_address · emailAddressEmail OR phone — at least one. | required | |
| Phone | phone_number · phone · phoneNumberEmail OR phone — at least one. | required |
| State | state · us_state · provinceNeeded for the close-rate map — leads without it can't be placed on any state. | recommended |
| Date of birth | date_of_birth · dob · birthdateImproves match confidence. | optional |
| ZIP | zip · zip_code · postal_code · postcode | optional |
| Address | street_address · address · address_line_1 · addr1 · address1 | optional |
| Campaign ID | campaign_id · campaignIdAttribution — which campaign produced the lead. GoHighLevel keeps no Meta ad ids of its own; this needs a custom field. | optional |
| Ad set ID | adset_id · adsetId · ad_set_id | optional |
| Ad ID | ad_id · adId | optional |
Unknown keys are ignored rather than rejected, so extra Zapier fields are harmless. A payload missing a required field returns 400 with invalid_payload and names the offending field.
# wrong or missing x-axiums-secret { "ok": false, "error": "unauthorized", "reason": "invalid_secret" } # no secret generated for this client yet { "ok": false, "error": "unauthorized", "reason": "no_secret_configured" } # the agent has not authorized this partner { "ok": false, "error": "forbidden", "reason": "link_not_authorized" }
Zapier's Test action sends a sample payload. AXIUMS recognizes it, marks the channel verified, and stores no lead — so testing a Zap never pollutes the agent's data. The response carries test: true.
A real lead returns leadId. Delivery is idempotent on the lead id: re-sending the same leadgen_id returns 200 with deduped: true and the original leadId, so a Zapier replay or retry cannot double-count a lead. Retry freely.
| Status | Body | Meaning |
|---|---|---|
| 200 | ok, test | Channel verified; nothing stored |
| 200 | ok, leadId | Lead accepted |
| 200 | ok, deduped | Already received; the original is returned |
| 400 | invalid_payload | A required field is missing or unparseable |
| 401 | invalid_token | Unknown webhook URL — it may have been regenerated |
| 401 | invalid_secret | Wrong or missing x-axiums-secret |
| 401 | no_secret_configured | No secret generated for this client yet |
| 403 | link_not_authorized | The agent has not approved, or has revoked |
| 429 | rate_limited | More than 60 accepted leads in a minute on one connection |
Leads are matched to policies on a rolling window after capture; a lead that never matches simply expires. Matching is not part of this endpoint's response.
# Zapier "Test action" sample { "ok": true, "test": true, "verified": true }
| Status | Code | Meaning |
|---|---|---|
| 400 | npn_missing | No NPN supplied |
| 400 | npn_invalid | NPN is not 4–12 digits |
| 400 | batch_too_large | More than 100 NPNs in one request |
| 400 | carrier_unknown | Carrier slug not recognized |
| 400 | invalid_cursor | The since cursor on a feed is malformed. Returned only to an authenticated caller |
| 401 | unauthorized | Key missing, malformed, revoked or expired |
| 404 | agent_unavailable | No agent available for this NPN under your access |
| 429 | rate_limited | Rate limit exceeded — see Retry-After |
| 500 | internal_error | Something failed on our side. Safe to retry |
It covers three cases with one identical response: the NPN doesn't exist, it exists but has no AXIUMS agent, or it belongs to an agent outside your agency. Distinguishing them would let any approved key enumerate which NPNs are in AXIUMS.
{ "error": "npn_invalid", "message": "NPN must be 4–12 digits." }
{ "error": "agent_unavailable", "message": "No agent available for this NPN under your API access." }
{ "error": "rate_limited", "message": "Rate limit exceeded. Retry after 30 seconds." }
| Limit | Value |
|---|---|
| Requests per minute | 120 |
| NPNs per batch request | 100 |
Rate limit state is returned on authenticated responses. These headers are absent on a 401, since an unauthenticated caller is given no rate state.
A 429 includes Retry-After in seconds. Retry with exponential backoff.
X-RateLimit-Limit: 120 X-RateLimit-Remaining: 119 X-RateLimit-Reset: 1786321140
The version is in the path. Breaking changes ship as a new version; v1 continues to be served. Additive changes — new fields, new carriers, new status values — can land in v1 without notice, so parse defensively and ignore unknown fields.
Technical questions and key rotation: api@axiums.ai
// New carriers and status values can appear // in v1 without notice. Never exhaustively // switch on syncStatus without a default. switch (c.syncStatus) { case "ok": return act(c); default: return review(c); }