Skip to content

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:

bash
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:

bash
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_166 when 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 for overlap_hours (default 24, maximum 168; 0 revokes it at once) so you can swap credentials without downtime. expires_in_days sets the replacement's lifetime. The response carries the new token (shown once) and replaced_token with grace_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 (optional reason; keeps the record, marks it revoked) or DELETE /api/v1/tokens/{token_id}
  • GET /api/v1/tokens lists your tokens (add include_revoked=true to see revoked ones); each has a status of active, rotating, revoked or expired. 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/token is the client-credentials grant (RFC 6749 §4.4) with a signed private_key_jwt client assertion. It is form-encoded (grant_type=client_credentials, client_assertion_type, client_assertion, optional scope that may only narrow the client's scopes, optional business_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/token issues access + refresh tokens for a user, authenticated with an HMAC-SHA256 signature over a partner client_id, timestamp and nonce; POST /api/v1/oauth2/refresh renews 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:

  1. The client discovers auth requirements via /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server.
  2. It registers via dynamic client registration and opens a browser to /oauth2/authorize.
  3. You sign in and approve scopes on the consent page.
  4. The client exchanges the code at /oauth2/mcp/token for 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.