Getting started
Authentication
Every request carries an API key as a bearer token. There are no other authentication methods, and no unauthenticated endpoints.
curl https://xosign.ai/api/v1/account \
-H "Authorization: Bearer xo_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"Issuing a key
Create keys yourself in Settings → API keys. Self-serve issuance is available on the Crew plan and above; the API itself is available to every paid plan.
- The key is shown once, at the moment you create it. We store only a hash, so we cannot show it to you again or recover it — if you lose it, revoke it and issue another.
- You choose the key’s scopes when you create it. Scopes cannot be changed afterwards — to widen access, issue a new key.
- Revoking takes effect immediately, and remains available even if your plan lapses, so you are never locked out of shutting off a leaked key.
Treat a key like a password
A key carries the full authority of its scopes over your entire account. Keep it in a secret store or an environment variable, never in version control, client-side code, or a mobile app.
Live and test keys
Keys come in two modes, distinguished by their prefix. GET /account echoes back the mode of whichever key you used, so you can always confirm which one is in play.
| Prefix | Mode | Behaviour |
|---|---|---|
xo_live_ | Live | Everything happens for real: documents are created and signature requests are delivered. |
xo_test_ | Test | Documents are created and change status exactly as in live mode, but no email or SMS is delivered. |
Test mode is not a sandbox
A xo_test_ key reads and writes your real production data, in the same account, alongside everything else. The only thing test mode changes is that outbound email and SMS are suppressed.
A document you create with a test key is a real document in your account. Sending one with a test key really does move it to sent. Test mode makes it safe to exercise the API without bothering your signers — it does not make it safe to experiment against data you care about. There is no separate test dataset today.
Scopes
Each key carries a fixed set of scopes, and each endpoint requires one. Calling an endpoint your key is not scoped for returns 403 with the code insufficient_scope — grant the narrowest set that does the job.
| Scope | Allows |
|---|---|
| documents:read | List and retrieve documents. |
| recipients:read | Read a document's recipients, including their email addresses and phone numbers. |
| documents:write | Create draft documents. |
| documents:send | Send documents for signature. This is the scope that causes email and SMS to go out. |
Reading recipients is separated from reading documents on purpose: an integration that only needs to know whether something was signed can be given documents:read alone, without ever seeing signers’ email addresses or phone numbers.
| Endpoint | Requires |
|---|---|
GET /account | No scope required |
GET /documents | documents:read |
POST /documents | documents:write |
GET /documents/{id} | documents:read |
POST /documents/{id}/send | documents:send |
GET /documents/{id}/recipients | recipients:read |
Keys issued before scopes existed
Older keys were granted read-only access when scopes were introduced, rather than being silently upgraded. If a long-lived key suddenly cannot create or send, that is why — issue a new one with the scopes you need.
Always call xosign.ai
A different host will look like a bad key
Send every request to https://xosign.ai/api/v1. If you call a deployment hostname instead, the request is redirected to the canonical host — and browsers, Node’s built-in fetch, and most HTTP clients drop the Authorization header when a redirect crosses origins.
The retried request then arrives with no credentials, and you get a 401 that looks exactly like an invalid key. If a key works with curl but not from your application, check the host you are calling before you suspect the key.
When authentication fails
Every authentication failure returns the same 401 body, whatever actually went wrong:
{
"error": {
"type": "authentication_error",
"code": "invalid_api_key",
"message": "Invalid API key.",
"doc_url": "https://xosign.ai/docs/api/errors#authentication_error",
"request_id": "req_b07b069f0a83492877e3fa31"
}
}A missing key is the one case with its own code (missing_authorization). Everything else — malformed, unknown, revoked, expired — is invalid_api_key, so the endpoint cannot be used to discover which keys are real. See authentication errors.