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.
Authenticate
Section titled “Authenticate”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.
ready — existence is not usability
Section titled “ready — existence is not usability”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: true | The org can sign. Safe to bind your resources to it. |
ready: false | The 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.
How it is decided
Section titled “How it is decided”ready = the org has an Org Master Seal AND its owner has registered a Personal SealBoth 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.
The state your gate should show
Section titled “The state your gate should show”Owning an organization and being able to use one are different things, and so are the messages:
| Situation | Tell the user |
|---|---|
| No Axowl account at all | They need to become an account owner first — see The account-owner gate |
| Account, no org | Create an organization on Axowl |
Org exists, ready: false | Setup isn’t finished — register the seal. Do not say “your organization was created” as if it succeeded |
ready: true, already bound | Choose a different organization |
ready: true, free | Proceed |
Endpoints that need explaining
Section titled “Endpoints that need explaining”Not the full list — that is the spec. These are the ones where calling them correctly is not obvious from the schema.
List the orgs an email owns
Section titled “List the orgs an email owns”POST /api/public/v1/orgs/by-ownerX-Api-Key: ah_live_…
{ "ownerEmail": "someone@example.com" }Returns every org that email owns, each with ready.
Match one org by name
Section titled “Match one org by name”POST /api/public/v1/orgs/match{ "ownerEmail": "…", "orgName": "Acme" }Read one org by id
Section titled “Read one org by id”POST /api/public/v1/orgs/by-idX-Api-Key: ah_live_…
{ "orgId": "8f3a…" }Returns { id, name, slug, ready }. 404 means no such org.
List an org’s app groups (brands)
Section titled “List an org’s app groups (brands)”POST /api/public/v1/orgs/app-groups{ "ownerEmail": "…", "orgName": "Acme" }Read one app group (brand) by id
Section titled “Read one app group (brand) by id”POST /api/public/v1/app-groups/by-idX-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.
The org behind the key
Section titled “The org behind the key”GET /api/public/v1/meImport end users (migration)
Section titled “Import end users (migration)”POST /api/public/v1/end-users/importX-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.
Billing statements
Section titled “Billing statements”GET /api/public/v1/invoicesGET /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.
Re-check on the server
Section titled “Re-check on the server”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.