{"openapi":"3.1.0","info":{"title":"POST /api/v1/tokens","version":"1.0.0","description":"Create token"},"servers":[{"url":"https://api.ondayzero.com","description":"Production"}],"paths":{"/api/v1/tokens":{"post":{"tags":["api-tokens"],"summary":"Create token","description":"Create a new API token. Requires two-factor authentication on the account and a recent verification of both factors in the current session; must be called from the DayZero web app, not with an API token.","operationId":"create_token","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/APITokenCreate"},"example":{"allowed_ips":["203.0.113.10","198.51.100.0/24"],"description":"Read-only token for the reporting assistant","expires_in_days":90,"name":"Month-end close agent","scopes":["read_only"]}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope_APITokenCreateResponse_"}}}},"400":{"description":"Bad Request - Invalid input","content":{"application/json":{"example":{"error":"validation_error","message":"Validation failed: due_date: Input should be a valid date","errors":{"due_date":"Input should be a valid date"},"code":"GEN_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Missing or expired token, an MCP OAuth token used against the REST API, or no access to this business","content":{"application/json":{"example":{"error":"unauthorized","message":"Authentication required","code":"AUTH_001","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Insufficient permissions","content":{"application/json":{"example":{"error":"forbidden","message":"You don't have permission to access this resource","code":"AUTH_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"validation_error","message":"Validation failed: amount: Input should be a valid integer","errors":{"amount":"Input should be a valid integer"},"code":"GEN_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"}}}}},"x-fastapi-operation-id":"create_token_api_v1_tokens_post"}}},"components":{"schemas":{"APITokenCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"Token name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional description"},"expires_in_days":{"anyOf":[{"type":"integer","maximum":365.0,"minimum":1.0},{"type":"null"}],"title":"Expires In Days","description":"Expiration in days (1-365, default: 365)","default":365},"business_ids":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":50},{"type":"null"}],"title":"Business Ids","description":"Limit the token to these businesses (UUIDs you can access). Omit for every business you can reach. A limited token gets 403 `AUTH_070` on any other business, and cannot elevate around it."},"scopes":{"anyOf":[{"items":{"type":"string","enum":["api","read_only","credit","credit:read","credit:payments"]},"type":"array"},{"type":"null"}],"title":"Scopes","description":"Token surface. Omit for full access. `read_only` blocks every mutating endpoint. `credit` limits the token to the partner-credit API; `credit:read` to its GET endpoints; `credit:payments` to POST /api/v1/credit/payments and its settlement/reversal paths, plus GET on a line's payoff-quote and payments."},"allowed_ips":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":32},{"type":"null"}],"title":"Allowed Ips","description":"When set, this token is refused (403 AUTH_028) unless the request comes from one of these IPv4/IPv6 addresses or CIDR ranges, or from a named cloud preset: `preset:google` (Google services, where Apps Script and Google Sheets call from) or `preset:aws:<region>` (Amazon EC2 in that region). Presets are shared by every customer of that cloud, so they are much weaker than your own addresses. Omit or leave empty to allow any IP. Set at minting; mint a new token to change the list."},"rate_limit_per_minute":{"anyOf":[{"type":"integer","maximum":6000.0,"minimum":10.0},{"type":"null"}],"title":"Rate Limit Per Minute","description":"Requests per minute this token may make (sliding window). Omit for the platform default. Over budget the API answers 429 AUTH_058 and the token's owner is alerted."}},"type":"object","required":["name"],"title":"APITokenCreate","description":"Request schema for creating a new API token.","example":{"allowed_ips":["203.0.113.10","198.51.100.0/24"],"description":"Read-only token for the reporting assistant","expires_in_days":90,"name":"Month-end close agent","scopes":["read_only"]},"examples":[{"allowed_ips":["203.0.113.10","198.51.100.0/24"],"description":"Read-only token for the reporting assistant","expires_in_days":90,"name":"Month-end close agent","scopes":["read_only"]}]},"APITokenCreateResponse":{"properties":{"token":{"type":"string","title":"Token","description":"The JWT token - save it now, you won't see it again!"},"token_info":{"$ref":"#/components/schemas/APITokenResponse","description":"Token metadata"}},"type":"object","required":["token","token_info"],"title":"APITokenCreateResponse","description":"Response schema when creating a new token (includes the JWT)."},"APITokenResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Token record ID"},"jti":{"type":"string","title":"Jti","description":"Unique token identifier (JWT ID)"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Human-readable token name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional token description"},"token_prefix":{"type":"string","title":"Token Prefix","description":"First characters of the token for display (e.g. dz_abc...)"},"token_type":{"type":"string","title":"Token Type","description":"Token type (always 'api')","default":"api"},"scopes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scopes","description":"Space-separated token scopes"},"allowed_ips":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Ips","description":"IPs/CIDRs this token may be used from; null means any IP"},"rate_limit_per_minute":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Limit Per Minute","description":"Requests per minute this token may make; null = platform default"},"business_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Business Ids","description":"Businesses this token is limited to; null means every business the owner can reach"},"status":{"type":"string","enum":["active","rotating","revoked","expired"],"title":"Status","description":"Lifecycle: `active`; `rotating` (replaced, still valid until `grace_until`); `revoked`; `expired`.","default":"active"},"rotated_from_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rotated From Id","description":"The token this one replaced, when minted by rotation"},"rotated_to_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rotated To Id","description":"The replacement token, once this one has been rotated"},"grace_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Grace Until","description":"After rotation: the instant this token stops working. Null when not rotated."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the token was created"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At","description":"Last time the token was used for authentication"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"When the token expires (null = never)"},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At","description":"When the token was revoked (null = active)"},"revocation_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revocation Reason","description":"Reason the token was revoked"}},"type":"object","required":["id","jti","token_prefix","created_at"],"title":"APITokenResponse","description":"Response schema for API token metadata (without the actual JWT)."},"SuccessEnvelope_APITokenCreateResponse_":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"},"data":{"anyOf":[{"$ref":"#/components/schemas/APITokenCreateResponse"},{"type":"null"}]}},"additionalProperties":true,"type":"object","title":"SuccessEnvelope[APITokenCreateResponse]"},"ErrorResponse":{"title":"ErrorResponse","type":"object","description":"Envelope returned by every non-2xx response. Branch on `code` (stable) rather than `message` (human-readable, may change).","required":["error","message","code"],"properties":{"error":{"type":"string","description":"Error category: `validation_error`, `unauthorized`, `forbidden`, `not_found`, `conflict`, `rate_limited`, `server_error`, or `service_unavailable`."},"message":{"type":"string","description":"Human-readable explanation, safe to show to end users."},"code":{"type":"string","description":"Stable machine-readable code in `CATEGORY_NNN` form (e.g. `NOT_FOUND_006`, `AUTH_010`, `GEN_002`)."},"request_id":{"type":"string","description":"Correlation id for support requests. Echoes the `x-request-id` request header when one was supplied."},"errors":{"type":"object","additionalProperties":{"type":"string"},"description":"Field-level validation messages keyed by field name. Present on 400/422 validation failures only."}}}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Token","description":"API token authentication. Format: `Bearer dz_...`"}}},"security":[{"BearerAuth":[]}]}