{"openapi":"3.1.0","info":{"title":"POST /api/v1/credit/sandbox/provision","version":"1.0.0","description":"Create the paired sandbox"},"servers":[{"url":"https://api.ondayzero.com","description":"Production"}],"paths":{"/api/v1/credit/sandbox/provision":{"post":{"tags":["credit"],"summary":"Create the paired sandbox","description":"Console operators only, on the live business. Creates \"<name> (Sandbox)\": a sandbox business with the same firm links and members (same roles), a copy of every credit program's configuration issuing from the mock issuer, and each program's active scorecard versions. No lines, loans or cards. Mints a `credit` API token limited to the sandbox and returns it once (`token`). Calling it again returns the same sandbox (`created: false`) and mints a token only with `mint_token: true`. DayZero staff acting by elevation get no membership and no token (`token_note`), and `mint_token: true` from them is 403 `CREDIT_584`. 400 `CREDIT_580` on a sandbox; 400 `CREDIT_582` when the paired business is no longer a sandbox.","operationId":"credit_provision_sandbox","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-business-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Business-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxProvisionRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope_SandboxProvisionResponse_"}}}},"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"}}}},"404":{"description":"Not Found - Resource does not exist","content":{"application/json":{"example":{"error":"not_found","message":"Resource not found","code":"NOT_FOUND_001","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded; back off and retry","content":{"application/json":{"example":{"error":"rate_limited","message":"Too many requests. Please slow down.","code":"GEN_005","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":"credit_provision_sandbox_api_v1_credit_sandbox_provision_post"}}},"components":{"schemas":{"SandboxProgramPair":{"properties":{"live_program_id":{"type":"string","title":"Live Program Id","description":"The program on the live business."},"sandbox_program_id":{"type":"string","title":"Sandbox Program Id","description":"Its copy on the sandbox."},"slug":{"type":"string","title":"Slug","description":"The sandbox program's slug: the live slug, unless the sandbox already had another program with it, then the live slug with `-2`, `-3`, ... added."},"display_name":{"type":"string","title":"Display Name","description":"The sandbox program's display name."},"issuing_provider":{"type":"string","title":"Issuing Provider","description":"Always `mock` on a sandbox copy: cards come from the mock issuer."},"scorecards":{"items":{"$ref":"#/components/schemas/SandboxScorecardCopy"},"type":"array","title":"Scorecards","description":"The live program's active scorecard versions, as copied."}},"type":"object","required":["live_program_id","sandbox_program_id","slug","display_name","issuing_provider"],"title":"SandboxProgramPair"},"SandboxProvisionRequest":{"properties":{"mint_token":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Mint Token","description":"Mint a `credit` API token limited to the sandbox and return it once. Left out, a token is minted only when this call creates the sandbox; send `true` to mint another for an existing sandbox, `false` to mint none. Minting needs what creating any API token needs: approved API access and a two-factor check in this session. DayZero staff acting by elevation (not members of the business) are never minted a token, and `true` from them is `403` (`CREDIT_584`): a member of the business mints it."},"token_expires_in_days":{"type":"integer","maximum":365.0,"minimum":1.0,"title":"Token Expires In Days","description":"Lifetime of the minted token, in days (1 to 365).","default":365}},"additionalProperties":false,"type":"object","title":"SandboxProvisionRequest"},"SandboxProvisionResponse":{"properties":{"live_business_id":{"type":"string","title":"Live Business Id","description":"The business the call was made on."},"sandbox_business_id":{"type":"string","title":"Sandbox Business Id","description":"The paired sandbox business."},"sandbox_business_name":{"type":"string","title":"Sandbox Business Name","description":"The sandbox's name, \"<live name> (Sandbox)\"."},"created":{"type":"boolean","title":"Created","description":"True when this call created the sandbox; false when the pair already existed and was returned as it is."},"members_copied":{"type":"integer","title":"Members Copied","description":"Memberships copied from the live business (people, firm staff assignments, client access, firm teams). 0 when not created."},"firm_links_copied":{"type":"integer","title":"Firm Links Copied","description":"Firm links copied from the live business. 0 when not created."},"programs":{"items":{"$ref":"#/components/schemas/SandboxProgramPair"},"type":"array","title":"Programs","description":"Every live program and its sandbox copy."},"token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token","description":"The minted API token. Shown once, here; DayZero keeps only its hash. Null when none was minted."},"token_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token Id","description":"The token's id, for listing and revoking it."},"token_scopes":{"items":{"type":"string"},"type":"array","title":"Token Scopes","description":"`[\"credit\"]` when a token was minted."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"When the minted token expires (UTC)."},"token_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token Note","description":"Why no token was minted when the caller is DayZero staff acting by elevation, and how a member of the business mints one. Null otherwise."}},"type":"object","required":["live_business_id","sandbox_business_id","sandbox_business_name","created","members_copied","firm_links_copied"],"title":"SandboxProvisionResponse"},"SandboxScorecardCopy":{"properties":{"live_scorecard_id":{"type":"string","title":"Live Scorecard Id","description":"The scorecard on the live program."},"sandbox_scorecard_id":{"type":"string","title":"Sandbox Scorecard Id","description":"Its copy on the sandbox program."},"name":{"type":"string","title":"Name","description":"The scorecard's name."},"live_version_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Live Version Id","description":"The live scorecard's active version. Null when it has none."},"sandbox_version_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sandbox Version Id","description":"The sandbox scorecard's active version: the copy (origin `sandbox_copy`), or a version activated on the sandbox since."},"version_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Version Number","description":"That version's number on the sandbox scorecard."}},"type":"object","required":["live_scorecard_id","sandbox_scorecard_id","name","live_version_id","sandbox_version_id","version_number"],"title":"SandboxScorecardCopy"},"SuccessEnvelope_SandboxProvisionResponse_":{"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/SandboxProvisionResponse"},{"type":"null"}]}},"additionalProperties":true,"type":"object","title":"SuccessEnvelope[SandboxProvisionResponse]"},"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":[]}]}