Authentication
All DayZero API requests require a Bearer token in the Authorization header. Almost every endpoint also needs a business context header, because one token can usually reach more than one business.
Obtaining a Token
API access is off by default. In Settings → Developers, request access (POST /api/v1/tokens/access-request, with use_case_kind, use_case and optionally company_name; one open request at a time, three submissions a day); a DayZero operator reviews every request and emails you when it is approved or denied. GET /api/v1/tokens/access shows where your request stands. Until access is approved, creating, rotating and listing tokens return 403 AUTH_069 (AUTH_047 if your firm has switched API access off for you).
Then generate an API token from the same page. A person creates every token in the dashboard: minting one requires two-factor authentication on the account, and the dashboard asks you to verify both factors when you generate it (403 AUTH_050 if the session has no second factor, AUTH_056 if the check is stale). There is no way to mint a token via the API — POST /api/v1/tokens refuses an API token with 403 AUTH_057, so a leaked key can never be used to create more keys.
The token is a long-lived signed JWT. The dashboard list identifies each token by a short dz_… prefix; dz_your_token_here in these guides stands for the full token you copied. When you create one you choose:
| Option | Values | Notes |
|---|---|---|
Name (name) |
text, 1–255 characters | Shown in the dashboard; use it to identify the integration |
| Description | text | Optional |
| Expiration | 1–365 days (default 365) | Expired tokens return 401 |
Scopes (scopes, a list) |
(omit), read_only, credit, credit:read, credit:payments |
Omit for full access. read_only allows only GET, HEAD and OPTIONS. credit / credit:read / credit:payments limit the token to the partner-credit API (all methods, GET only, or payment writes plus a line's payoff quote and payment list). A credit scope cannot be combined with read_only or full access |
| Businesses | up to 50 business UUIDs you can access | Optional (business_ids). The token works only on those businesses; any other returns 403 AUTH_070. A token limited to exactly one business may omit x-business-id |
| Allowed IPs | IPv4, IPv6, CIDR, or a cloud preset (preset:google, preset:aws:<region>) |
Optional, up to 32 entries. When set, requests from any other address return 403 AUTH_028. Presets are shared by every customer of that cloud, so they are much weaker than your own addresses. Set at minting; mint a new token to change the list |
| Rate limit | 10–6,000 requests per minute (rate_limit_per_minute) |
Optional; the default is 600. Over budget the API answers 429 AUTH_058 with Retry-After and alerts the token's owner |
The plaintext token is shown once, in the dashboard. Store it; it cannot be retrieved again. Create a separate token per integration so one can be revoked without breaking the others.
Making Authenticated Requests
Start by listing the businesses the token can access — this is the one common endpoint that does not need x-business-id:
curl "https://api.ondayzero.com/api/v1/businesses" \
-H "Authorization: Bearer dz_your_token_here" \
-H "Accept: application/json"Then send both headers on every other request:
curl "https://api.ondayzero.com/api/v1/invoices?limit=20" \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: 01912345-abcd-7000-8000-000000000001" \
-H "Accept: application/json"| Header | Required | Description |
|---|---|---|
Authorization |
Yes | Bearer dz_... token |
x-business-id |
Yes, on business-scoped endpoints | UUID of the business. Operations that need it list it as a required header parameter in the API Reference |
Accept |
Recommended | application/json guarantees JSON error bodies (HTML error pages are only rendered for browser-style text/html requests) |
Content-Type |
On requests with a body | application/json |
Authentication errors
| Status | code |
Meaning |
|---|---|---|
401 |
AUTH_008 |
x-business-id header missing on an endpoint that requires it |
401 |
AUTH_010 |
The token cannot access that business |
401 |
AUTH_059 |
The token was rotated and its overlap window has closed; use the replacement |
401 |
other AUTH_* |
Token missing, malformed, expired or revoked |
403 |
AUTH_048 |
Token is valid but its scope (e.g. read_only) does not permit the operation, or a credit-scoped token strayed outside the credit API |
403 |
AUTH_070 |
The token is limited to specific businesses and this is not one of them |
403 |
AUTH_072 |
The business's subscription is not active, so API tokens cannot act on it |
403 |
AUTH_028 |
Token is valid but the request did not come from an allowed IP |
403 |
AUTH_057 |
The operation needs a recent identity check an API token cannot provide (for example POST /api/v1/tokens). Do it in the dashboard while signed in |
429 |
AUTH_058 |
The token exceeded its requests-per-minute budget; wait for Retry-After |
Apart from 429, which you can retry after waiting, none of these are worth retrying unchanged — fix the header or the token. See Error Handling for the response envelope.
Token Limits and Revocation
- Maximum 10 active tokens per user (
VALIDATION_166when exceeded) - Optional expiration between 1 and 365 days. Mint a replacement before expiry, swap the integration, then revoke the old token so nothing breaks mid-swap
- Optional IP allowlist (
allowed_ips), business limit (business_ids) and request budget (rate_limit_per_minute) per token, all set on create - Rotate with
POST /api/v1/tokens/{token_id}/rotate(same two-factor step-up as creating): it mints a replacement that inherits the name, scopes, IP allowlist and rate limit, and keeps the old token valid foroverlap_hours(default 24, maximum 168;0revokes it at once) so you can swap credentials without downtime.expires_in_dayssets the replacement's lifetime. The response carries the newtoken(shown once) andreplaced_tokenwithgrace_until; a token can be rotated once, so rotate the replacement next time - Revoke at any time from Settings → Developers, or with
POST /api/v1/tokens/{token_id}/revoke(optionalreason; keeps the record, marks it revoked) orDELETE /api/v1/tokens/{token_id} GET /api/v1/tokenslists your tokens (addinclude_revoked=trueto see revoked ones); each has astatusofactive,rotating,revokedorexpired. Plaintext values are never returned
Partner OAuth 2.0
DayZero also runs OAuth 2.0 token services for approved partner integrations. They are provisioned per partner and are not self-serve — for your own integrations, use API tokens.
POST /api/v1/oauth2/partner/tokenis the client-credentials grant (RFC 6749 §4.4) with a signedprivate_key_jwtclient assertion. It is form-encoded (grant_type=client_credentials,client_assertion_type,client_assertion, optionalscopethat may only narrow the client's scopes, optionalbusiness_id) and returns a short-lived bearer token (access_token,token_type,expires_in,scope). Errors use the RFC 6749 shape (error,error_description,error_code).POST /api/v1/oauth2/tokenissues access + refresh tokens for a user, authenticated with an HMAC-SHA256 signature over a partnerclient_id, timestamp and nonce;POST /api/v1/oauth2/refreshrenews them. It never issues tokens for DayZero staff accounts, and is only served on deployments where it is enabled.
AI Assistants (MCP)
The DayZero MCP server (https://mcp.ondayzero.com/mcp) lets AI assistants like Claude, ChatGPT, Cursor and Codex interact with your accounting data through OAuth 2.1:
- The client discovers auth requirements via
/.well-known/oauth-protected-resourceand/.well-known/oauth-authorization-server. - It registers via dynamic client registration and opens a browser to
/oauth2/authorize. - You sign in and approve scopes on the consent page.
- The client exchanges the code at
/oauth2/mcp/tokenfor MCP-scoped tokens.
Important: MCP tokens and REST API tokens are separate credential types:
| Credential | Works on MCP | Works on REST API |
|---|---|---|
MCP OAuth token (type=mcp) |
✓ | ✗ |
REST API key (type=api) |
✗ | ✓ |
SPA session token (type=access) |
✗ | ✓ |
Review and revoke MCP-connected clients with GET /api/v1/oauth/grants and DELETE /api/v1/oauth/grants/{grant_id}. REST API keys are revoked separately (see above).
See the MCP Server guide for setup, scopes, and troubleshooting, or Using DayZero from an AI Agent if you are building an agent against the REST API.