AXIUMSAPI v1Developer documentation
Base URL https://api.axiums.ai/v1

AXIUMS API

The 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.

Quick start
curl https://api.axiums.ai/v1/requirements?npn=8241905573 \
  -H "Authorization: Bearer axm_live_7f3c9a12e8b4d6f0a5c1"

Requesting access

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.

What your key can reach

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.

Request access

Requests are reviewed by a person. We'll be in touch by email.

Key format
Example key
axm_live_7f3c9a12e8b4d6f0a5c1

# Never commit this. Never paste it into
# a support ticket, chat, or email.

Authentication

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.

Authorization header
-H "Authorization: Bearer axm_live_7f3c9a12e8b4d6f0a5c1"
Unauthorized
401
{
  "error": "unauthorized",
  "message": "API key is missing, malformed, revoked or expired."
}

Carrier coverage

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.

CarrierCoverageNotes
TransamericafullRequirement items with instructions and comments
Mutual of OmahafullOutstanding case requirements with requested dates
ForestersfullPending-issue requirements
Liberty Bankersstatus_onlyReturns an initialPremiumNotPaid flag per policy, no itemized requirements
EthosunavailableNo requirements extraction built
CorebridgeunavailableNo requirements extraction built
American Home LifeunavailableNo requirements extraction built

Coverage expands over time. Branch on the coverage value rather than hardcoding which carriers return requirements.

An unavailable carrier
JSON
{
  "carrier": "ethos",
  "coverage": "unavailable",
  "syncStatus": "unavailable",
  "lastSyncedAt": null,
  "fieldProvenance": {},
  "requirements": []
}

Data freshness

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.

ValueMeaning
okLast sync succeeded within 48 hours
staleLast success was more than 48 hours ago
failingThe most recent sync attempt did not succeed, or the credential needs reauthorization
not_connectedThe agent has not connected this carrier
unavailableAXIUMS has no requirements extraction for this carrier
Branch on syncStatus, never on array length

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.”

Two empty arrays, opposite meanings
JSON
// 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
}

Field provenance

Every carrier block includes a fieldProvenance map marking where each field's value came from.

carrier_verbatim vs axiums_derived

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
JSON
"fieldProvenance": {
  "policyNumber":  "carrier_verbatim",
  "policyStatus":  "carrier_verbatim",
  "requirement":   "carrier_verbatim",
  "comments":      "carrier_verbatim",
  "requestedDate": "carrier_verbatim",
  "insuredName":   "axiums_derived"
}

Get requirements for one agent

GET/v1/requirements

Returns every carrier block for a single agent, identified by National Producer Number.

npnstringRequired

The agent's National Producer Number. Must be 4–12 digits.

carrierstringOptional

Restrict to one carrier: transamerica, mutual-of-omaha, foresters, liberty-bankers.

statusstringOptional

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.

Nullable fields

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.

Request
curl "https://api.axiums.ai/v1/requirements?npn=8241905573" \
  -H "Authorization: Bearer $AXIUMS_API_KEY"
Response
200 · application/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 }
      ]
    }
  ]
}

Requirements for multiple agents

POST/v1/requirements/batch

Every requested NPN appears in the response with an explicit status. A batch never silently drops an NPN.

npnsstring[]Required

Array of NPNs. Maximum 100 per request.

carrierstringOptional

Restrict all agents to one carrier.

statusstringOptional

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.

Request
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" }'
Response
200 · application/json
{
  "requested": 2,
  "summary": { "found": 1, "unavailable": 1 },
  "results": [
    {
      "agent": { "npn": "8241905573", "name": "Jordan Ellery" },
      "status": "found",
      "carriers": [ /* … */ ]
    },
    {
      "npn": "6017384492",
      "status": "unavailable"
    }
  ]
}

Requirement updates feed

GET/v1/requirements/updates

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.

npnstringRequired

The agent's NPN, within your agency.

sincestringOptional

Opaque cursor from a previous response's next_cursor. Omit to read from the beginning.

limitnumberOptional

Page size, 1–500 (default 100).

Cursor semantics

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.

Event types

typemeaning
APPEAREDA new requirement was captured.
CHANGEDA field changed; diff carries the old→new values (e.g. a comment or deadline edit).
RESOLVEDThe requirement is no longer on the carrier.
REAPPEAREDA previously resolved requirement returned.

Fields

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.

Freshness

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.

Consent

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.

Polling recipe

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.

Request
curl
curl "https://api.axiums.ai/v1/requirements/updates?npn=8241905573&since=CURSOR" \
  -H "Authorization: Bearer sk_live_…"
Response
200 · application/json
{
  "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…"
}

Account changes feed

GET/v1/changes

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.

Two ways to authenticate

This is the only v1 endpoint that accepts either credential.

CredentialScopenpn
Agency key (axm_live_…)The named agent, within your agencyRequired
Agent token (axm_agent_…)That agent onlyIgnored

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.

npnstringOptional

The agent's NPN. Required with an agency key, ignored with an agent token.

sincestringOptional

Opaque cursor from a previous response's next_cursor. Omit to read from the beginning.

limitnumberOptional

Page size, 1–500 (default 100).

categorystringOptional

Comma-separated categories, e.g. LAPSE_DETECTED,CHARGEBACK_POSTED.

carrierstringOptional

Carrier slug, e.g. mutual-of-omaha.

fromstringOptional

ISO date — only events at or after it.

tostringOptional

ISO date — only events at or before it.

scopestringOptional

own, downline, or all (default).

Cursor semantics

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.

Fields

Each event carries id, category, carrier, carrierLabel, policyNumber, insuredName, field, oldValue, newValue, at, syncLogId and scope.

Scope and insured names

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.

What is excluded

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.

Consent

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.

Error ordering

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.

Request
curl · agency key
curl "https://api.axiums.ai/v1/changes?npn=8241905573&limit=50" \
  -H "Authorization: Bearer axm_live_…"
curl · agent token
curl "https://api.axiums.ai/v1/changes?scope=own" \
  -H "Authorization: Bearer axm_agent_…"
Response
200 · application/json
{
  "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…"
}

List policies

GET/api/v1/policies

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.

ParameterMeaning
npnAgency 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.
cursorOpaque; pass back the previous response's next_cursor. A null cursor means the last page.
limit1–200, default 50.
carrierCarrier slug, e.g. mutual-of-omaha.
statusPolicy status, e.g. IN_FORCE, LAPSED.
placed_from / placed_toISO dates bounding placedDate.

Policy detail

GET/api/v1/policies/{carrier}/{policyNumber}

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.

Request
GET
curl -H "Authorization: Bearer axm_agent_…" \
  "https://www.axiums.ai/api/v1/policies?carrier=mutual-of-omaha&limit=50"
Detail
GET
curl -H "Authorization: Bearer axm_agent_…" \
  "https://www.axiums.ai/api/v1/policies/mutual-of-omaha/BU6347993"
Not found
404 · application/json
{
  "found": false,
  "message": "No policy with that number in your book."
}

MCP server

POST/api/mcp

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.

Tools

ToolReturns
get_my_requirementsOutstanding carrier requirements, with the per-carrier map of which contact fields that carrier actually provides.
get_my_bookUp to 150 most recently placed policies: insured, product, carrier, status, annualised premium, advance and backend.
get_my_chargebacksOwn advance clawbacks and the override clawbacks the agent carries.
get_my_comp_structureComp level, advance/backend split, per-carrier chargeback schedules, true comp rate per product.
get_policy_detailsEverything 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_changesWhat 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.

Sharing

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.

Connecting a client

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.

Connect
server address
https://www.axiums.ai/api/mcp
Bearer token (no OAuth)
claude mcp add
claude mcp add --transport http axiums \
  https://www.axiums.ai/api/mcp \
  --header "Authorization: Bearer axm_agent_…"
Discovery
GET
curl https://www.axiums.ai/.well-known/oauth-protected-resource
curl https://www.axiums.ai/.well-known/oauth-authorization-server

Zapier lead intake

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.

Endpoint

POST https://axiums.ai/api/marketing/hooks/lead/<token> with Content-Type: application/json.

Authentication

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.

Send a lead
cURL
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"}'
Accepted
200
{ "ok": true, "deduped": false, "leadId": "cmt..." }

Lead field mapping

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.

FieldMap to any ofNeeded
Lead IDleadgen_id · leadgenId · id · lead_id · contact_id · contactId
The 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 timecreated_time · created_at · createdTime · created · date_created · dateCreated
When the consumer submitted. Anchors the match window.
required
Namefirst_name + last_name · full_name · name
Either the two parts or the full name.
required
Emailemail · email_address · emailAddress
Email OR phone — at least one.
required
Phonephone_number · phone · phoneNumber
Email OR phone — at least one.
required
Statestate · us_state · province
Needed for the close-rate map — leads without it can't be placed on any state.
recommended
Date of birthdate_of_birth · dob · birthdate
Improves match confidence.
optional
ZIPzip · zip_code · postal_code · postcodeoptional
Addressstreet_address · address · address_line_1 · addr1 · address1optional
Campaign IDcampaign_id · campaignId
Attribution — which campaign produced the lead. GoHighLevel keeps no Meta ad ids of its own; this needs a custom field.
optional
Ad set IDadset_id · adsetId · ad_set_idoptional
Ad IDad_id · adIdoptional

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.

Rejections
JSON
# 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" }

Test vs real leads

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.

StatusBodyMeaning
200ok, testChannel verified; nothing stored
200ok, leadIdLead accepted
200ok, dedupedAlready received; the original is returned
400invalid_payloadA required field is missing or unparseable
401invalid_tokenUnknown webhook URL — it may have been regenerated
401invalid_secretWrong or missing x-axiums-secret
401no_secret_configuredNo secret generated for this client yet
403link_not_authorizedThe agent has not approved, or has revoked
429rate_limitedMore 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.

Test action
200
# Zapier "Test action" sample
{ "ok": true, "test": true, "verified": true }

Errors

StatusCodeMeaning
400npn_missingNo NPN supplied
400npn_invalidNPN is not 4–12 digits
400batch_too_largeMore than 100 NPNs in one request
400carrier_unknownCarrier slug not recognized
400invalid_cursorThe since cursor on a feed is malformed. Returned only to an authenticated caller
401unauthorizedKey missing, malformed, revoked or expired
404agent_unavailableNo agent available for this NPN under your access
429rate_limitedRate limit exceeded — see Retry-After
500internal_errorSomething failed on our side. Safe to retry
Why agent_unavailable is deliberately ambiguous

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 bodies
400 · npn_invalid
{
  "error": "npn_invalid",
  "message": "NPN must be 4–12 digits."
}
404 · agent_unavailable
{
  "error": "agent_unavailable",
  "message": "No agent available for this NPN under your API access."
}
429 · rate_limited
{
  "error": "rate_limited",
  "message": "Rate limit exceeded. Retry after 30 seconds."
}

Rate limits

LimitValue
Requests per minute120
NPNs per batch request100

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.

Response headers
HTTP
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 119
X-RateLimit-Reset: 1786321140

Versioning

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.

Support

Technical questions and key rotation: api@axiums.ai

Parse defensively
Node
// 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);
}