Skip to content

Public API (ah_live_)

/api/public/v1 is the documented surface for external developers. It is versioned: the contract is stable and breaking changes go to v2.

X-Api-Key: ah_live_…

An Org API key identifies one organization and returns that org’s data only — never another org’s. Issue and rotate keys in Settings → Keys. Keys are SHA-256 hashed at rest and shown once.

Every org DTO carries a ready boolean. It is the single most important field on this surface.

{ "id": "", "name": "Acme", "slug": "acme", "ready": false }
Meaning
ready: trueThe org can sign. Safe to bind your resources to it.
ready: falseThe org exists but cannot sign yet. Binding to it produces a shell that fails later.

Gate on ready, never on mere existence. An org that isn’t ready will accept your binding and then fail at the moment something has to be sealed — far from where the mistake was made.

ready = the org has an Org Master Seal
AND its owner has registered a Personal Seal

Both halves are required. A corporation’s Master Seal is auto-issued at creation, so on its own it is always true and would never catch an owner who skipped sealing. The owner’s Personal Seal is the real signal.

Owning an organization and being able to use one are different things, and so are the messages:

SituationTell the user
No Axowl account at allThey need to become an account owner first — see The account-owner gate
Account, no orgCreate an organization on Axowl
Org exists, ready: falseSetup isn’t finished — register the seal. Do not say “your organization was created” as if it succeeded
ready: true, already boundChoose a different organization
ready: true, freeProceed

Not the full list — that is the spec. These are the ones where calling them correctly is not obvious from the schema.

POST /api/public/v1/orgs/by-owner
X-Api-Key: ah_live_…
{ "ownerEmail": "someone@example.com" }

Returns every org that email owns, each with ready.

POST /api/public/v1/orgs/match
{ "ownerEmail": "", "orgName": "Acme" }
POST /api/public/v1/orgs/by-id
X-Api-Key: ah_live_…
{ "orgId": "8f3a…" }

Returns { id, name, slug, ready }. 404 means no such org.

POST /api/public/v1/orgs/app-groups
{ "ownerEmail": "", "orgName": "Acme" }
POST /api/public/v1/app-groups/by-id
X-Api-Key: ah_live_…
{ "appGroupId": "1c9d…" }

Returns { id, orgId, name, description, iconUrl, brandColor }. 404 means no such group — it may have been deleted.

An App Group is the brand: the org is the company, and each App Group is one product or service line. If you are listing brands, read the name and branding from here instead of asking the customer to type them into a second form. Two hand-entered copies of one brand name drift apart the day either side is edited, and the customer has no way to tell which one is showing.

orgId comes back so you can confirm the group still belongs to the company you think it does, rather than trusting a binding you stored earlier.

Every App Group of that org — for partners that treat an App Group as a brand (the org is the company; an App Group is one product/service line, and its products live inside it). An empty list is a normal answer: an org may not use App Groups yet. First consumer: Bullmark’s “Select Your Brand (App Group)” dropdown.

GET /api/public/v1/me
POST /api/public/v1/end-users/import
X-Api-Key: ah_live_…
{ "appGroupId": "", "source": "clerk", "sendInvites": false, "users": [ ] }

Bulk-imports users exported from another auth provider (Clerk, Kinde, Supabase, Firebase, Auth0, NextAuth …) into one App Group — max 500 rows per request, each row answering independently (created / duplicate / invalid) with the new endUserId for your foreign-key re-mapping. Password hashes, when supplied, are stored dormant (provenance only — no login path reads them): imported users sign in passwordlessly with the same email. The full walkthrough per source platform lives in the Migration guides.

GET /api/public/v1/invoices
GET /api/public/v1/invoices/{id}

Monthly invoices for the calling org. Axowl is prepaid — an invoice records what was charged and whether your credit covered it, never a balance due. Every line carries the ledger reference it was charged under, so amounts are verifiable independently of this API. See Invoices.

Treat anything a browser hands back as a claim. A user can navigate straight to your form without ever visiting Axowl, so re-run the same ready check server-side before you commit the work. This is the rule the payment flow already follows: the redirect is a hint, the server decides.