Skip to main content
# Machines Cash Docs — agents.llm

> Single-file bundle for coding agents. Contains repo instructions, navigation, and full content of all docs pages and key specs.

Last updated: 2026-02-22

## How to use this file
- Use this file as the primary context when coding agents interact with this repo or API.
- Keep changes minimal and scoped; update this file whenever source docs change.
- Preserve formatting and code fences inside embedded files; do not rewrite examples unless asked.

## Agent instructions
### Project structure
- Documentation lives in root-level *.mdx files.
- docs.json controls navigation, theme, and top-level site config.
- openapi.yaml is the canonical API reference (OpenAPI 3.x).
- password-gate.js is a client-side password gate (deterrent only).
- Static assets live in the repo root (e.g., *.svg, favicon.ico).

### Commands
- No build, dev, or test commands are defined for this repo.

### Editing rules
- Keep frontmatter at the top of each MDX file (title, description).
- Match existing tone: short, direct sentences; avoid marketing fluff.
- Use fenced code blocks with a language tag (bash, json, ts, etc.).
- When adding a new page, add the file in the repo root and update docs.json navigation.
- Avoid changing openapi.yaml unless the API contract changed.

### Do / Don't
Do:
- Keep diffs small and scoped to the requested change.
- Preserve existing examples unless explicitly asked to update them.
- Update docs.json navigation when adding or removing pages.

Don't:
- Don't add new dependencies or tooling without an explicit request.
- Don't remove pages or reorder navigation without direction.

### Architecture notes
- This is a static docs site: MDX pages + docs.json + openapi.yaml.
- API reference pages are generated from openapi.yaml.

### Verification
- Manually review MDX/JSON/YAML formatting; there is no automated build or test runner.

### Agentic API notes (from the docs)
- Base URLs: https://api.machines.cash/partner/v1 (prod) and https://dev-api.machines.cash/partner/v1 (sandbox).
- Use X-Partner-Key for session creation and user resolve; use Authorization: Bearer <SESSION_TOKEN> after that.
- Use Idempotency-Key on card creation and other idempotent writes.
- Use propose -> execute for sensitive actions.
- Return small, structured results with a summary for LLM chaining.
- Prefer cursor pagination and search + get-by-id pairs.
- Implement bounded retries on network timeouts.
- For tool JSON Schemas, see agent-tool-schemas.mdx; keep additionalProperties: false and explicit required.

### Partner deposit + withdrawal endpoint index (current)
Deposits:
- GET /partner/v1/deposits/assets
- POST /partner/v1/deposits/range
- POST /partner/v1/deposits/estimate
- POST /partner/v1/deposits
- GET /partner/v1/deposits
- GET /partner/v1/deposits/{depositId}

Withdrawals:
- GET /partner/v1/withdrawals/assets
- POST /partner/v1/withdrawals/range
- POST /partner/v1/withdrawals/estimate
- POST /partner/v1/withdrawals

### Canonical partner flows
Deposit flow:
1) assets -> 2) range -> 3) estimate -> 4) create -> 5) status poll

Deposit estimate request example:
{
  "currency": "hbar",
  "network": "hbar",
  "amount": 25,
  "amountCurrency": "crypto"
}

Deposit create responses include:
- deposit.depositAddress
- deposit.payinExtraId (memo/tag when required)

Withdrawal flow:
1) assets -> 2) range -> 3) estimate -> 4) create -> 5) execute onchain

Important: destination and route coverage are dynamic.
Always query `assets`, `range`, and `estimate` before creating withdrawals.
`source.contractId` in withdrawal create is optional.

Withdrawal estimate request example:
{
  "destination": {
    "currency": "hbar",
    "network": "hbar",
    "extraId": "optional-tag"
  },
  "amountCents": 2500
}

Withdrawal create request body (breaking schema):
{
  "amountCents": 2500,
  "source": {
    "contractId": "optional-uuid"
  },
  "destination": {
    "currency": "hbar",
    "network": "hbar",
    "address": "0.0.123456",
    "extraId": "optional-tag"
  },
  "adminAddress": "0x..."
}

Withdrawal create responses include relay metadata:
- relay.changeNowId
- relay.payinAddress
- relay.payinExtraId
- relay.payoutAddress
- relay.payoutExtraId
- relay.fromCurrency / relay.fromNetwork
- relay.toCurrency / relay.toNetwork

### Privy integration notes
- Partners using Privy embedded wallets should execute withdrawals server-side with `execution.callTarget` from `POST /partner/v1/withdrawals`.
- Support both call paths: `controller_v1` (7 args) and `coordinator_v2` (10 args + admin typed data signature).
- Required wallet methods: `eth_sendTransaction`; add `eth_signTypedData_v4` for `coordinator_v2`.
- Use idempotency keys for withdrawal create retries and embedded tx submission retries.
- Use sponsored transactions for embedded wallets where available in your Privy config.

### Safety and data handling
- Never expose X-Partner-Key, session tokens, or decrypted PAN/CVC in client code or logs.
- Card secrets are encrypted; decrypt only in trusted server-side contexts.

## Page index (docs.json navigation order)
- index.mdx: Machines Cash API
- getting-started.mdx: Quickstart — End-to-end flow for KYC, deposits, and cards.
- authentication.mdx: Authentication — Use your API key to mint short‑lived session tokens. Keep it simple and secure.
- encryption.mdx: Encryption — Encrypt card labels using server‑side helpers.
- sandbox-testing.mdx: Sandbox Testing — Use the sandbox environment to validate KYC, cards, and deposits before production.
- partners/privy.mdx: Privy Embedded Wallet Integration — Integrate Machines Partner API with Privy embedded wallets for server-managed signing.
- kyc-flow.mdx: KYC Flow — Collect and verify identity.
- agreements.mdx: Agreements — Display required user agreements after KYC approval.
- cards.mdx: Cards — Create, lock/unlock, delete, and retrieve encrypted card secrets.
- balances.mdx: Balances — Fetch spending power and charge totals.
- deposits.mdx: Deposits — Discovery, range, estimate, and create flow for partner deposits.
- withdrawals.mdx: Withdrawals — Source-specific discovery/range/estimate plus relay-backed create and execution.
- agentic-payments.mdx: Agentic Payments — Build reliable, safe-by-construction agent flows for card issuance and spending.
- agent-tool-schemas.mdx: Agent Tool Schemas — Copy-paste JSON Schemas for tools to call the API.
- best-practices-agents.mdx: Agent Best Practices — Guardrails and patterns that keep agent calls safe and reliable.
- errors.mdx: Errors & Retries — Deterministic error codes and retry guidance for agents and services.
- kyc-values.mdx: KYC Field Values — Expected values for occupation, salary, account purpose, and monthly volume.

## Additional MDX pages (not in docs.json navigation)
- billing.mdx: Billing — Per-card pricing by default; additional billable events optional.
- disposable-cards.mdx: Disposable Cards — Disposable proposal and execute flow.
- webhooks.mdx: Webhooks — Subscribe to card + transaction + KYC updates with HMAC signatures.

## Pages (full content)
### In navigation order
#### File: index.mdx
````mdx
---
title: "Machines Cash API"
description: ""
---

<Card title="Start with the Quickstart" icon="rocket" href="/getting-started" />
<Card title="Auth & Sessions" icon="key" href="/authentication" />
<Card title="Cards" icon="credit-card" href="/cards" />
<Card title="Balances" icon="gauge" href="/balances" />
<Card title="Deposits" icon="wallet" href="/deposits" />
<Card title="Withdrawals" icon="arrow-up-right" href="/withdrawals" />
<Card title="API Reference" icon="book" href="/api-reference" />
<Card title="Sandbox Testing" icon="flask" href="/sandbox-testing" />

<AccordionGroup>
  <Accordion title="What is Machines Cash?">
    Machines Cash issues scoped virtual Visa cards backed by onchain collateral, enabling private spending through a unified API.
  </Accordion>
  <Accordion title="Who is this for?">
    Developers who want to embed KYC, card issuance, and withdrawals without touching issuer credentials.
  </Accordion>
</AccordionGroup>
````

#### File: getting-started.mdx
````mdx
---
title: "Quickstart"
description: "End-to-end flow for KYC, deposits, and cards."
---

Base URLs:
- Production: `https://api.machines.cash/partner/v1`
- Sandbox: `https://dev-api.machines.cash/partner/v1`

Get API access: email `help@machines.cash` or DM `@erturkarda` on Telegram.

All requests are JSON. Use `X-Partner-Key` for steps 1-2. Use `Authorization: Bearer <SESSION_TOKEN>` for the rest.

## 1. Create a session token
Call <a href="/api/sessions" target="_blank" rel="noreferrer">POST /sessions</a>.

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/sessions \
  --header 'Content-Type: application/json' \
  --header 'X-Partner-Key: <PARTNER_API_KEY>' \
  --data '
{
  "userId": "eeetest",
  "scopes": [
    "kyc.read",
    "kyc.write"
  ],
  "walletAddress": "0x462B1eD1B62F4c282c0EA342BCefcF8f9919226E",
  "ttlSeconds": 9001
}
'
```

```json
{
  "ok": true,
  "data": {
    "sessionToken": "<SESSION_TOKEN>",
    "sessionId": "<SESSION_ID>",
    "userId": "eeetest",
    "expiresAt": "2026-01-16T16:47:52.000Z",
    "scopes": [
      "kyc.read",
      "kyc.write"
    ]
  },
  "summary": "session created",
  "errors": []
}
```

Use this session token for steps 3-9.

## 2. Create or link the user
Call <a href="/api/users/resolve" target="_blank" rel="noreferrer">POST /users/resolve</a>.

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/users/resolve \
  --header 'Content-Type: application/json' \
  --header 'X-Partner-Key: <PARTNER_API_KEY>' \
  --data '
{
  "userId": "eeetest",
  "walletAddress": "0x462B1eD1B62F4c282c0EA342BCefcF8f9919226E",
  "walletLabel": "Main Wallet"
}
'
```

```json
{
  "ok": true,
  "data": {
    "userId": "eeetest",
    "walletAddress": "0x462b1ed1b62f4c282c0ea342bcefcf8f9919226e",
    "kycStatus": "not_submitted",
    "createdAt": "2026-01-16T14:17:51.298Z"
  },
  "summary": "user linked; kyc not started",
  "errors": []
}
```

## 3. Get KYC field requirements and values
Call <a href="/api/kyc/values" target="_blank" rel="noreferrer">GET /kyc/values</a> to build your KYC form. The full occupation list is also available at <a href="/kyc-values">/kyc-values</a>.

```bash
curl --request GET \
  --url https://api.machines.cash/partner/v1/kyc/values \
  --header 'Authorization: Bearer <SESSION_TOKEN>'
```

```json
{
  "ok": true,
  "data": {
    "fields": [
      {
        "name": "firstName",
        "required": true,
        "type": "string",
        "maxLength": 50,
        "description": "Given name."
      },
      {
        "name": "lastName",
        "required": true,
        "type": "string",
        "maxLength": 50,
        "description": "Family name."
      },
      {
        "name": "birthDate",
        "required": true,
        "type": "date",
        "description": "YYYY-MM-DD (e.g., 1990-01-01)."
      },
      {
        "name": "nationalId",
        "required": true,
        "type": "string",
        "regex": "^[0-9A-Za-z-]+$",
        "description": "Government ID number. Letters, numbers, and dashes only."
      },
      {
        "name": "countryOfIssue",
        "required": true,
        "type": "string",
        "minLength": 2,
        "maxLength": 2,
        "description": "ISO-3166-1 alpha-2 (e.g., US)."
      },
      {
        "name": "email",
        "required": true,
        "type": "string",
        "description": "Valid email address."
      },
      {
        "name": "address",
        "required": true,
        "type": "object",
        "description": "Object with line1, line2, city, region, postalCode, countryCode."
      },
      {
        "name": "phoneCountryCode",
        "required": false,
        "type": "string",
        "maxLength": 3,
        "description": "Country calling code digits only (e.g., 1)."
      },
      {
        "name": "phoneNumber",
        "required": false,
        "type": "string",
        "maxLength": 15,
        "description": "Phone number digits only; include area code."
      },
      {
        "name": "occupation",
        "required": true,
        "type": "string",
        "description": "SOC occupation code (e.g., 49-3023). Use /kyc/values for the list."
      },
      {
        "name": "annualSalary",
        "required": true,
        "type": "string",
        "enum": [
          "<40k",
          "50k–99k",
          "100k–149k",
          "150k+"
        ],
        "description": "Choose a salary range."
      },
      {
        "name": "accountPurpose",
        "required": true,
        "type": "string",
        "enum": [
          "everyday spend",
          "subscriptions",
          "business expenses",
          "testing",
          "other"
        ],
        "description": "How the account will be used."
      },
      {
        "name": "expectedMonthlyVolume",
        "required": true,
        "type": "string",
        "enum": [
          "under $1k",
          "$1k–$5k",
          "$5k–$20k",
          "$20k+"
        ],
        "description": "Choose a monthly volume range."
      }
    ],
    "occupations": [
      { "code": "11-1021", "label": "General and Operations Managers" },
      { "code": "11-2011", "label": "Advertising and Promotions Managers" },
      { "code": "11-3031", "label": "Financial Managers" },
      { "code": "11-9021", "label": "Construction Managers" },
      { "code": "11-9041", "label": "Architectural and Engineering Managers" },
      { "code": "11-9071", "label": "Gaming Managers" },
      { "code": "11-9141", "label": "Property, Real Estate, and Community Association Managers" },
      { "code": "13-1041", "label": "Compliance Officers" },
      { "code": "13-2011", "label": "Accountants and Auditors" },
      { "code": "13-2051", "label": "Financial Analysts" },
      { "code": "13-2052", "label": "Personal Financial Advisors" },
      { "code": "13-2082", "label": "Tax Preparers" },
      { "code": "15-1121", "label": "Computer Systems Analysts" },
      { "code": "15-1131", "label": "Computer Programmers" },
      { "code": "15-1132", "label": "Software Developers, Applications" },
      { "code": "15-1133", "label": "Software Developers, Systems Software" },
      { "code": "15-1141", "label": "Database Administrators" },
      { "code": "15-1142", "label": "Information Security Analysts" },
      { "code": "15-1143", "label": "Computer Network Architects" },
      { "code": "15-1151", "label": "Computer User Support Specialists" },
      { "code": "17-2051", "label": "Civil Engineers" },
      { "code": "17-2071", "label": "Electrical Engineers" },
      { "code": "17-2141", "label": "Mechanical Engineers" },
      { "code": "19-3011", "label": "Economists" },
      { "code": "23-1011", "label": "Lawyers" },
      { "code": "23-2011", "label": "Paralegals and Legal Assistants" },
      { "code": "25-2021", "label": "Elementary School Teachers" },
      { "code": "27-1024", "label": "Graphic Designers" },
      { "code": "27-2012", "label": "Producers and Directors" },
      { "code": "29-1141", "label": "Registered Nurses" },
      { "code": "29-1062", "label": "Family and General Practitioners" },
      { "code": "29-1067", "label": "Surgeons" },
      { "code": "31-9097", "label": "Phlebotomists" },
      { "code": "33-3021", "label": "Detectives and Criminal Investigators" },
      { "code": "35-1011", "label": "Chefs and Head Cooks" },
      { "code": "41-9011", "label": "Demonstrators and Product Promoters" },
      { "code": "41-9021", "label": "Real Estate Brokers" },
      { "code": "43-3071", "label": "Tellers" },
      { "code": "47-1011", "label": "Construction Supervisors" },
      { "code": "47-2061", "label": "Construction Laborers" },
      { "code": "49-3023", "label": "Automotive Service Technicians and Mechanics" },
      { "code": "51-4121", "label": "Welders, Cutters, Solderers, and Brazers" },
      { "code": "53-3032", "label": "Heavy and Tractor-Trailer Truck Drivers" },
      { "code": "53-3041", "label": "Taxi Drivers and Chauffeurs" },
      { "code": "SELFEMP", "label": "Self-Employed" },
      { "code": "UNEMPLO", "label": "Unemployed" },
      { "code": "RETIRED", "label": "Retired" },
      { "code": "OTHERXX", "label": "Other" }
    ],
    "annualSalary": ["<40k", "50k–99k", "100k–149k", "150k+"],
    "expectedMonthlyVolume": ["under $1k", "$1k–$5k", "$5k–$20k", "$20k+"],
    "accountPurpose": ["everyday spend", "subscriptions", "business expenses", "testing", "other"]
  },
  "summary": "kyc values",
  "errors": []
}
```

## 4. Submit KYC
Call <a href="/api/kyc/applications" target="_blank" rel="noreferrer">POST /kyc/applications</a>.

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/kyc/applications \
  --header 'Authorization: Bearer <SESSION_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "firstName": "John",
  "lastName": "smith",
  "birthDate": "1990-01-01",
  "nationalId": "123456789",
  "countryOfIssue": "US",
  "email": "jsmith@example.com",
  "address": {
    "line1": "123 Main St",
    "city": "New York",
    "region": "NY",
    "postalCode": "10001",
    "countryCode": "US",
    "line2": "Unit 4"
  },
  "occupation": "49-3023",
  "annualSalary": "<40k",
  "accountPurpose": "everyday spend",
  "expectedMonthlyVolume": "under $1k",
  "phoneCountryCode": "1",
  "phoneNumber": "4155551234"
}
'
```

```json
{
  "ok": true,
  "data": {
    "status": "needs_verification",
    "reason": "",
    "completionLink": {
      "url": "https://kyc.machines.cash/verify",
      "params": {
        "userId": "18717ac1-c375-4ff6-972d-79dd702b7d44",
        "signature": "<KYC_SIGNATURE>"
      }
    },
    "externalVerificationLink": {
      "url": "https://kyc.machines.cash/verify",
      "params": {
        "userId": "18717ac1-c375-4ff6-972d-79dd702b7d44",
        "signature": "<KYC_SIGNATURE>"
      }
    },
    "isActive": true,
    "isTermsOfServiceAccepted": false
  },
  "summary": "kyc submitted",
  "errors": []
}
```

## 5. Complete verification
Open the `completionLink` (or `externalVerificationLink`) in a new window so the user can finish verification.

## 6. Poll KYC status
Call <a href="/api/kyc/status" target="_blank" rel="noreferrer">GET /kyc/status</a> until the status is `approved`. Status meanings are listed in <a href="/kyc-flow#response-status">KYC status reference</a>.

```bash
curl --request GET \
  --url https://api.machines.cash/partner/v1/kyc/status \
  --header 'Authorization: Bearer <SESSION_TOKEN>'
```

```json
{
  "ok": true,
  "data": {
    "status": "approved",
    "reason": "",
    "completionLink": null,
    "externalVerificationLink": null,
    "isActive": null,
    "isTermsOfServiceAccepted": false
  },
  "summary": "kyc status",
  "errors": []
}
```

## 7. Show agreements and collect consent
Call <a href="/api/agreements/list" target="_blank" rel="noreferrer">GET /agreements</a> to fetch agreement text, then <a href="/api/agreements/accept" target="_blank" rel="noreferrer">POST /agreements</a> to accept.

```bash
curl --request GET \
  --url https://api.machines.cash/partner/v1/agreements \
  --header 'Authorization: Bearer <SESSION_TOKEN>'
```

```json
{
  "ok": true,
  "data": {
    "agreements": [
      {
        "id": "esignConsent",
        "text": "I accept the E-Sign Consent",
        "links": [
          { "label": "E-Sign Consent", "url": "https://machines.cash/e-sign" }
        ]
      },
      {
        "id": "accountOpeningPrivacyNotice",
        "text": "I accept the Account Opening Privacy Notice",
        "links": [
          { "label": "Account Opening Privacy Notice", "url": "https://machines.cash/account-opening-privacy" }
        ]
      },
      {
        "id": "cardTermsAndIssuerPrivacyPolicy",
        "text": "I accept the Machines Card Terms and the Issuer Privacy Policy",
        "links": [
          { "label": "Machines Card Terms", "url": "https://machines.cash/machines-card-terms" },
          { "label": "Issuer Privacy Policy", "url": "https://www.third-national.com/privacypolicy" }
        ]
      },
      {
        "id": "informationCertification",
        "text": "I certify that the information I have provided is accurate and that I will abide by all the rules and requirements related to my Machines Spend Card.",
        "links": []
      },
      {
        "id": "solicitationAcknowledgement",
        "text": "I acknowledge that applying for the Machines Spend Card does not constitute unauthorized solicitation.",
        "links": []
      }
    ],
    "accepted": false,
    "acceptedAt": null
  },
  "summary": "agreements",
  "errors": []
}
```

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/agreements \
  --header 'Authorization: Bearer <SESSION_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "accepted": true
}
'
```

```json
{
  "ok": true,
  "data": {
    "agreements": [
      {
        "id": "esignConsent",
        "text": "I accept the E-Sign Consent",
        "links": [
          { "label": "E-Sign Consent", "url": "https://machines.cash/e-sign" }
        ]
      },
      {
        "id": "accountOpeningPrivacyNotice",
        "text": "I accept the Account Opening Privacy Notice",
        "links": [
          { "label": "Account Opening Privacy Notice", "url": "https://machines.cash/account-opening-privacy" }
        ]
      },
      {
        "id": "cardTermsAndIssuerPrivacyPolicy",
        "text": "I accept the Machines Card Terms and the Issuer Privacy Policy",
        "links": [
          { "label": "Machines Card Terms", "url": "https://machines.cash/machines-card-terms" },
          { "label": "Issuer Privacy Policy", "url": "https://www.third-national.com/privacypolicy" }
        ]
      },
      {
        "id": "informationCertification",
        "text": "I certify that the information I have provided is accurate and that I will abide by all the rules and requirements related to my Machines Spend Card.",
        "links": []
      },
      {
        "id": "solicitationAcknowledgement",
        "text": "I acknowledge that applying for the Machines Spend Card does not constitute unauthorized solicitation.",
        "links": []
      }
    ],
    "accepted": true,
    "acceptedAt": "2026-01-16T16:22:48.186Z"
  },
  "summary": "agreements accepted",
  "errors": []
}
```

## 8. Discover and create a deposit
Use the deposit flow in this order:
1. <a href="/api/deposits/assets" target="_blank" rel="noreferrer"><code>GET /deposits/assets</code></a>
2. <a href="/api/deposits/range" target="_blank" rel="noreferrer"><code>POST /deposits/range</code></a>
3. <a href="/api/deposits/estimate" target="_blank" rel="noreferrer"><code>POST /deposits/estimate</code></a>
4. <a href="/api/deposits/create" target="_blank" rel="noreferrer"><code>POST /deposits</code></a>

List assets:

```bash
curl --request GET \
  --url 'https://api.machines.cash/partner/v1/deposits/assets?q=hbar&limit=20' \
  --header 'Authorization: Bearer <SESSION_TOKEN>'
```

Get range:

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/deposits/range \
  --header 'Authorization: Bearer <SESSION_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data '{"currency":"hbar","network":"hbar"}'
```

Get estimate:

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/deposits/estimate \
  --header 'Authorization: Bearer <SESSION_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data '{"currency":"hbar","network":"hbar","amount":25,"amountCurrency":"crypto"}'
```

Create deposit:

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/deposits \
  --header 'Authorization: Bearer <SESSION_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data '{"currency":"hbar","network":"hbar","amount":25}'
```

Example create response fields:
- `deposit.depositAddress`: send funds here.
- `deposit.payinExtraId`: memo/tag to include when required by source network.

## 9. Check deposit status and balances
Use <a href="/api/deposits/get" target="_blank" rel="noreferrer">GET /deposits/{depositId}</a> to check status. Balances typically refresh within a few minutes after completion.

```bash
curl --request GET \
  --url https://api.machines.cash/partner/v1/deposits/<DEPOSIT_ID> \
  --header 'Authorization: Bearer <SESSION_TOKEN>'
```

Fetch balances with <a href="/api/balances" target="_blank" rel="noreferrer">GET /balances</a>:

```bash
curl --request GET \
  --url https://api.machines.cash/partner/v1/balances \
  --header 'Authorization: Bearer <SESSION_TOKEN>'
```

```json
{
  "ok": true,
  "data": {
    "balances": {
      "creditLimit": 3000,
      "pendingCharges": 0,
      "postedCharges": 0,
      "balanceDue": 0,
      "spendingPower": 3000
    }
  },
  "summary": "balances",
  "errors": []
}
```

## Optional: Quote a withdrawal route
Withdrawal flow uses:
1. <a href="/api/withdrawals/assets" target="_blank" rel="noreferrer"><code>GET /withdrawals/assets</code></a>
2. <a href="/api/withdrawals/range" target="_blank" rel="noreferrer"><code>POST /withdrawals/range</code></a>
3. <a href="/api/withdrawals/estimate" target="_blank" rel="noreferrer"><code>POST /withdrawals/estimate</code></a>
4. <a href="/api/withdrawals/create" target="_blank" rel="noreferrer"><code>POST /withdrawals</code></a>

Withdrawal routes are dynamic (BTC/SOL/EVM and others). Always query `assets`, `range`, and `estimate` before creating a withdrawal. For create requests, `source.contractId` is optional.

Example estimate request:

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/withdrawals/estimate \
  --header 'Authorization: Bearer <SESSION_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data '{
    "destination": {
      "currency": "hbar",
      "network": "hbar",
      "extraId": "optional-tag"
    },
    "amountCents": 2500
  }'
```

Create responses include `relay` metadata (`payinAddress`, `payinExtraId`, `payoutAddress`, `payoutExtraId`) and `execution` fields used for onchain signing/broadcast. Execute from the same `adminAddress` you used in create, and send the tx to `execution.callTarget` (not the collateral proxy). For `coordinator_v2`, include the additional admin typed-data signature inputs.

## 10. Create a card session token
For card creation, labels, and card secrets, mint a new session token with <a href="/api/sessions" target="_blank" rel="noreferrer">POST /sessions</a>. Use this token for steps 11-14.

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/sessions \
  --header 'Content-Type: application/json' \
  --header 'X-Partner-Key: <PARTNER_API_KEY>' \
  --data '
{
  "userId": "eeetest",
  "scopes": [
    "crypto.read",
    "crypto.write",
    "cards.secrets.read",
    "cards.write",
    "cards.read"
  ],
  "walletAddress": "0x462B1eD1B62F4c282c0EA342BCefcF8f9919226E",
  "ttlSeconds": 9001
}
'
```

```json
{
  "ok": true,
  "data": {
    "sessionToken": "<SESSION_TOKEN>",
    "sessionId": "<SESSION_ID>",
    "userId": "eeetest",
    "expiresAt": "2026-01-17T16:32:47.000Z",
    "scopes": [
      "crypto.read",
      "crypto.write",
      "cards.secrets.read",
      "cards.write",
      "cards.read"
    ]
  },
  "summary": "session created",
  "errors": []
}
```

## 11. Encrypt a card label (optional)
If you want to display a card label to your users, encrypt it with <a href="/api/encryption/encrypt" target="_blank" rel="noreferrer">POST /encryption/encrypt</a> and pass it as `encryptedName` when creating the card.

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/encryption/encrypt \
  --header 'Authorization: Bearer <CARD_SESSION_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "value": "Marketing Spend"
}
'
```

```json
{
  "ok": true,
  "data": {
    "value": {
      "v": 1,
      "iv": "Qz4f2m7Hk1P9x0ab",
      "ct": "Hdbb2yT2F4xWZL5m5bJX3eJb4n8wVhjPzqLw2h7i"
    }
  },
  "summary": "encrypted",
  "errors": []
}
```

## 12. Create a card
Call <a href="/api/cards/create" target="_blank" rel="noreferrer">POST /cards</a> with an optional card label and limit.

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/cards \
  --header 'Authorization: Bearer <CARD_SESSION_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "encryptedName": {
    "v": 1,
    "iv": "Qz4f2m7Hk1P9x0ab",
    "ct": "Hdbb2yT2F4xWZL5m5bJX3eJb4n8wVhjPzqLw2h7i"
  },
  "limit": {
    "amountCents": 2500,
    "frequency": "perAuthorization"
  }
}
'
```

```json
{
  "ok": true,
  "data": {
    "cardId": "9f970d1a-fd8e-41ac-a6fd-5993417942e3",
    "status": "active",
    "brand": "VISA",
    "last4": "4242",
    "expirationMonth": 12,
    "expirationYear": 2029
  },
  "summary": "card created",
  "errors": []
}
```

## 13. Create a card secrets session
Call <a href="/api/cards/secrets/session" target="_blank" rel="noreferrer">POST /cards/secrets/session</a>.

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/cards/secrets/session \
  --header 'Authorization: Bearer <CARD_SESSION_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data '{}'
```

```json
{
  "ok": true,
  "data": {
    "sessionId": "<CARD_SECRETS_SESSION_ID>",
    "secretKey": "<CARD_SECRETS_SECRET_KEY>"
  },
  "summary": "card secrets session",
  "errors": []
}
```

## 14. Fetch card secrets
Call <a href="/api/cards/secrets" target="_blank" rel="noreferrer">POST /cards/{cardId}/secrets</a> with the session id from the previous step. This returns encrypted PAN and CVC only (not the card label).

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/cards/9f970d1a-fd8e-41ac-a6fd-5993417942e3/secrets \
  --header 'Authorization: Bearer <CARD_SESSION_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "sessionId": "<CARD_SECRETS_SESSION_ID>"
}
'
```

```json
{
  "ok": true,
  "data": {
    "encryptedPan": {
      "iv": "TTPaDqoFqscIQriDfJ/Efg==",
      "data": "Mo3n8fEUM0yZpaopmfQvaIPmSy8YgEZpyGLbU7vqqvM="
    },
    "encryptedCvc": {
      "iv": "QG5uolhSt7b9MseCnH7ljA==",
      "data": "ruOb8ZRovieujsg34n0EhGWb5w=="
    }
  },
  "summary": "card secrets",
  "errors": []
}
```

## 15. Decrypt PAN and CVC (server-side)
Use the `secretKey` from Step 13 to decrypt the card number and CVC. This step is typically handled server-side (or by an agent) and never exposed to the browser.

```ts
import { createDecipheriv } from "crypto";

function decrypt({ iv, data }, keyHex) {
  const key = Buffer.from(keyHex, "hex");
  const payload = Buffer.from(data, "base64");
  const tag = payload.subarray(payload.length - 16);
  const ciphertext = payload.subarray(0, payload.length - 16);
  const decipher = createDecipheriv("aes-128-gcm", key, Buffer.from(iv, "base64"));
  decipher.setAuthTag(tag);
  return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
}

console.log("PAN:", decrypt(encryptedPan, secretKey));
console.log("CVC:", decrypt(encryptedCvc, secretKey));
```
````

#### File: authentication.mdx
````mdx
---
title: "Authentication"
description: "Use your API key to mint short‑lived session tokens. Keep it simple and secure."
---

## Two credentials
- API key: send with <code>X-Partner-Key</code> for server‑to‑server calls (mint sessions). Never expose it to clients.
- Session token: short‑lived JWT for protected endpoints. Send with <code>Authorization: Bearer &lt;token&gt;</code>.

## Flow
```mermaid
sequenceDiagram
  participant Backend as Your Backend
  participant MachinesAPI as Machines API
  participant Agent as Agent/Service

  Backend->>MachinesAPI: POST /users/resolve (X-Partner-Key)
  Backend->>MachinesAPI: POST /sessions (X-Partner-Key + scopes)
  MachinesAPI-->>Backend: { sessionToken, scopes, expiresAt }
  Backend->>Agent: Deliver sessionToken (scoped JWT)
  Agent->>MachinesAPI: call protected endpoints (Bearer sessionToken)
  MachinesAPI-->>Agent: structured JSON responses
```

## API flows (step‑by‑step)
<Steps>
  <Step title="Create a session token (server-to-server)">
    <b>Endpoint</b>: <a href="/api/sessions" target="_blank" rel="noreferrer"><code>POST /sessions</code></a><br/>
    <b>Body</b>:
    <ul>
      <li><code>userId</code> (string, your user id)</li>
      <li><code>walletAddress</code> (0x… address, optional)</li>
      <li><code>scopes</code> (array of strings)</li>
      <li><code>ttlSeconds</code> (integer, optional)</li>
    </ul>
    <b>Response</b>:
    <ul>
      <li><code>sessionToken</code> (use as <code>Authorization: Bearer &lt;token&gt;</code>)</li>
      <li><code>sessionId</code>, <code>userId</code>, <code>expiresAt</code>, <code>scopes</code></li>
    </ul>

```bash
curl -s \
  -d '{"userId":"demo-123","walletAddress":"0xabc...","scopes":["cards.read","cards.write"]}' \
  https://dev-api.machines.cash/partner/v1/sessions
```

```json
{
  "ok": true,
  "data": {
    "sessionToken": "<jwt>",
    "sessionId": "...",
    "userId": "...",
    "expiresAt": "2026-01-01T00:00:00.000Z",
    "scopes": ["cards.read","cards.write"]
  },
  "summary": "session created",
  "errors": []
}
```
  </Step>
  <Step title="Use the session token">
    Send <code>Authorization: Bearer &lt;token&gt;</code> on protected endpoints.

```bash
curl -s \
  -H 'Authorization: Bearer <SESSION_TOKEN>' \
  https://dev-api.machines.cash/partner/v1/cards
```

    Missing/expired tokens return <code>401</code>; missing permissions return <code>403</code>.
  </Step>
  <Step title="Renew when expired">
    Tokens are short‑lived. Mint a new session when you get <code>401 Unauthorized</code>.
  </Step>
  <Step title="Scope presets by task">
    Keep scopes minimal. Common sets:
    <ul>
      <li><b>KYC</b>: <code>kyc.read</code>, <code>kyc.write</code></li>
      <li><b>Cards</b>: <code>cards.read</code>, <code>cards.write</code></li>
      <li><b>Card secrets</b>: <code>cards.secrets.read</code></li>
      <li><b>Card labels</b>: <code>encryption.write</code> (encrypt), <code>encryption.read</code> (decrypt)</li>
      <li><b>Balances</b>: no extra scopes</li>
      <li><b>Withdrawals</b>: <code>withdrawals.write</code></li>
    </ul>
  </Step>
</Steps>

<Tip>Missing a scope returns <code>403 Forbidden</code>.</Tip>

## All available scopes
Deposits and balances do not require extra scopes.
```text
users.read
users.write
kyc.read
kyc.write
cards.read
cards.write
cards.secrets.read
encryption.read
encryption.write
deposits.read
deposits.write
withdrawals.write
```

## Headers quick reference
```text
# Mint sessions (server-to-server)
X-Partner-Key: <YOUR_API_KEY>

# Call protected endpoints
Authorization: Bearer <SESSION_TOKEN>
Content-Type: application/json

# Optional Open Responses output
X-Open-Responses: 1
X-Open-Responses-Call-Id: <TOOL_CALL_ID>
```

## Token basics
- Short‑lived by default (e.g., ~15 minutes). Rotate by minting a new session.
- Session payload includes account id, user id, wallet address, scopes, expiry.
- Never log tokens or card data; always use HTTPS.

<Note>Use <code>https://dev-api.machines.cash</code> in sandbox. Switch to production only after key validation.</Note>
````

#### File: encryption.mdx
````mdx
---
title: "Encryption"
description: "Encrypt card labels using server‑side helpers."
---

Machines cares about privacy—and this extends to API partners. We use end‑to‑end encryption for card UI metadata so only your users can read it. Use our helper endpoints so you don’t have to implement cryptography yourself.

What’s encrypted for cards:
- Card label (name) → <code>encryptedName</code>
- Color → <code>encryptedColor</code>
- Emoji → <code>encryptedEmoji</code>
- Memo/notes → <code>encryptedMemo</code>

<Note>Card secrets (PAN/CVC) are different. Use <code>POST /cards/secrets/session</code> + <code>POST /cards/{"{cardId}"}/secrets</code>. See <a href="/cards">Cards</a> → Secrets.</Note>

<Tip>Prerequisite: have a session token. See Authentication.</Tip>

## API flows (step‑by‑step)
<Steps>
  <Step title="Encrypt a card label (server-side)">
    <b>Endpoint</b>: <a href="/api/encryption/encrypt" target="_blank" rel="noreferrer"><code>POST /encryption/encrypt</code></a><br/>
    <b>Body</b>: JSON with a <code>value</code> string (e.g., "Ops Card").<br/>
    <b>Response</b>: returns an object with <code>value</code> (EncryptedField)

```bash
curl -s \
  -d '{"value":"Ops Card"}' \
  https://dev-api.machines.cash/partner/v1/encryption/encrypt
```
  </Step>
  <Step title="Decrypt a card label (server-side)">
    <b>Endpoint</b>: <a href="/api/encryption/decrypt" target="_blank" rel="noreferrer"><code>POST /encryption/decrypt</code></a><br/>
    <b>Body</b>: JSON with a <code>value</code> EncryptedField.
    <b>Response</b>: plaintext card label in <code>value</code>

```bash
curl -s \
  -d '{"value":{"v":1,"iv":"Qz4f2m7Hk1P9x0ab","ct":"Hdbb2yT2F4xWZL5m5bJX3eJb4n8wVhjPzqLw2h7i"}}' \
  https://dev-api.machines.cash/partner/v1/encryption/decrypt
```
  </Step>
</Steps>

## Fields
<ParamField path="value" type="string" required>
  Plaintext to encrypt. For decryption, pass the encrypted object returned by <code>/encryption/encrypt</code>.
</ParamField>

## Response (encrypt)
<ResponseField name="value.v" type="number" required>
  Schema version.
</ResponseField>
<ResponseField name="value.iv" type="string" required>
  Base64url IV.
</ResponseField>
<ResponseField name="value.ct" type="string" required>
  Base64url ciphertext + auth tag.
</ResponseField>

## Card labels are optional
- You can omit <code>encryptedName</code> when creating a card and set it later with <code>PATCH /cards/{"{cardId}"}</code>.
- When you do set a card label, always use <code>/encryption/encrypt</code> and <code>/encryption/decrypt</code> (no manual cryptography needed).

## Default flow (server-side)
```mermaid
sequenceDiagram
  participant Backend as Backend
  participant MachinesAPI as Machines API

  Backend->>MachinesAPI: POST /encryption/encrypt { value }
  MachinesAPI-->>Backend: { value: encryptedField }
  Backend->>MachinesAPI: POST /cards { encryptedName }
```

## Example (server-side)
```ts
const labelRes = await fetch("https://dev-api.machines.cash/partner/v1/encryption/encrypt", {
  method: "POST",
  headers: { Authorization: `Bearer ${sessionToken}`, "Content-Type": "application/json" },
  body: JSON.stringify({ value: "Ops Card" }),
});
const { data: encrypted } = await labelRes.json();

await fetch("https://dev-api.machines.cash/partner/v1/cards", {
  method: "POST",
  headers: { Authorization: `Bearer ${sessionToken}`, "Content-Type": "application/json" },
  body: JSON.stringify({ encryptedName: encrypted.value }),
});
```

## Display card labels in your frontend (recommended)
1) List cards: <code>GET /cards</code>
2) Decrypt each <code>encryptedName</code> with <code>POST /encryption/decrypt</code>
3) Send plaintext labels to your frontend

```ts
const cardsRes = await fetch("https://dev-api.machines.cash/partner/v1/cards", {
  headers: { Authorization: `Bearer ${sessionToken}` },
});
const { data: cardsData } = await cardsRes.json();

const labels = await Promise.all(
  cardsData.cards.map(async (card: { encryptedName: unknown; cardId: string }) => {
    const decryptRes = await fetch("https://dev-api.machines.cash/partner/v1/encryption/decrypt", {
      method: "POST",
      headers: { Authorization: `Bearer ${sessionToken}`, "Content-Type": "application/json" },
      body: JSON.stringify({ value: card.encryptedName }),
    });
    const { data } = await decryptRes.json();
    return { cardId: card.cardId, label: data.value };
  }),
);
```

<Note>Use <code>/encryption/decrypt</code> only for card labels and metadata. Card secrets (PAN/CVC) use <code>/cards/secrets/session</code> + <code>/cards/{"{cardId}"}/secrets</code>.</Note>

## Advanced (client-side, optional)
If you need client-side encryption, fetch a per-user data key via <code>GET /encryption/data-key</code> (requires <code>encryption.read</code>) and encrypt locally using AES-256-GCM.

### Encrypted field shape
```
{
  "v": 1,
  "iv": "<base64url>",
  "ct": "<base64url>"
}
```

- Canonical order is <code>v</code>, <code>iv</code>, <code>ct</code> (order doesn’t matter in JSON).
- <code>v</code>: schema version (currently 1)
- <code>iv</code>: 12-byte IV, base64url encoded
- <code>ct</code>: ciphertext + auth tag, base64url encoded

### Example (Node.js)
```ts
import { createCipheriv, randomBytes } from "node:crypto";

function base64Url(buf: Buffer) {
  return buf.toString("base64").replace(/\\+/g, "-").replace(/\\//g, "_").replace(/=+$/g, "");
}

export function encryptField(plaintext: string, dataKeyBase64Url: string) {
  const key = Buffer.from(dataKeyBase64Url, "base64url");
  const iv = randomBytes(12);
  const cipher = createCipheriv("aes-256-gcm", key, iv);
  const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
  const tag = cipher.getAuthTag();
  const combined = Buffer.concat([ciphertext, tag]);
  return { v: 1, iv: base64Url(iv), ct: base64Url(combined) };
}
```

<Note>Card PAN/CVC secrets use a secrets session and AES-128-GCM. See <a href="/cards">Cards</a> for that flow.</Note>
````

#### File: sandbox-testing.mdx
````mdx
---
title: "Sandbox Testing"
description: "Use the sandbox environment to validate KYC, cards, and deposits before production."
---

## Environments
Use separate API keys per environment.

| Environment | Base URL |
| --- | --- |
| Sandbox | <code>https://dev-api.machines.cash</code> |
| Production | <code>https://api.machines.cash</code> |


## Sandbox KYC shortcut
To bypass KYC in sandbox, set the applicant's <code>lastName</code> to <code>approved</code> when calling <code>POST /kyc/applications</code>. The next status check returns <code>approved</code>.

## Sandbox deposits (rUSD on Base Sepolia)
Sandbox deposits use rUSD on Base Sepolia. You need Base Sepolia ETH for gas.

### 1) Create a deposit intent
```
POST /deposits
{
  "currency": "rusd",
  "network": "base",
  "amount": 25
}
```

The response includes <code>deposit.id</code> and <code>deposit.depositAddress</code> (the Base Sepolia deposit address).

### 2) Mint rUSD
Mint rUSD on Base Sepolia, then send it to <code>deposit.depositAddress</code>.

- Chain: Base Sepolia (<code>84532</code>)
- rUSD contract: <code>0x10b5Be494C2962A7B318aFB63f0Ee30b959D000b</code>
- Mint function: <code>mint(uint256 amountDollars_Max100)</code>
- Max per mint tx: 100 rUSD

### 3) Track status
Poll until the deposit completes:
```
GET /deposits/{"{depositId}"}
```

<Note>Sandbox deposits are for testing only. Production deposits use USDC on Base.</Note>
````

#### File: partners/privy.mdx
````mdx
---
title: "Privy Embedded Wallet Integration"
description: "Integrate Machines Partner API with Privy embedded wallets for server-managed signing."
---

This guide is for partners already using Privy embedded wallets.

Scope:
- Partner API integration (`/partner/v1`) only.
- Server-side Privy wallet control for signing and sending transactions.
- Not a guide for Machines first-party web auth endpoints like `/v1/auth/email/exchange`.

## Architecture

- Your backend calls Machines Partner API.
- Your backend also calls Privy server APIs for embedded wallet signing/transaction execution.
- End users do not need wallet popups for embedded-wallet withdrawal execution paths.

```mermaid
sequenceDiagram
  participant App as Partner App Backend
  participant Machines as Machines Partner API
  participant Privy as Privy Wallet API
  participant Chain as Blockchain

  App->>Machines: POST /partner/v1/users/resolve
  App->>Machines: POST /partner/v1/sessions
  App->>Machines: POST /partner/v1/withdrawals
  Machines-->>App: withdrawal + execution(callTarget, callPath)
  App->>Privy: sign typed data (v2 path only)
  App->>Privy: eth_sendTransaction (to execution.callTarget)
  Privy-->>App: transactionId/hash
  Chain-->>App: confirmed tx
```

## Prerequisites

- Privy app configured for your environment.
- Privy embedded Ethereum wallets enabled.
- Privy authorization key configured for server-side wallet access.
- Machines partner API key.
- Partner backend can securely store:
  - Machines partner key
  - Privy app secret
  - Privy authorization private key

## Privy Embedded Wallet Checklist (Server-Side)

Before sending live traffic, confirm:

1. Wallet control path
   - You can resolve each user’s Privy embedded Ethereum wallet.
   - Your backend can sign/send on that wallet using an authorization context (authorization key, user JWT, or a custom sign function).
2. Signer binding
   - The wallet is configured with a signer path your backend can use (for example an authorization-key signer).
   - The authorization private key is in a secrets manager, never in client code.
3. Policy attachment
   - Policy IDs for withdrawal execution are attached to the signer/wallet.
   - Rules cover `eth_sendTransaction` and `eth_signTypedData_v4`.
4. Sponsorship setup
   - Gas sponsorship is enabled in Privy Dashboard for the chains you support.
   - Your backend passes `sponsor: true` for sponsored sends.
5. Status monitoring
   - You have webhook or polling support for transaction status reconciliation.

## Recommended Data to Persist

Per user, persist at least:
- `machinesUserId` (your Machines partner user mapping)
- `privyUserId` (Privy user id)
- `embeddedWalletAddress` (and `walletId` if you store it)
- `linkedAt` / `lastUsedAt`

Per execution, persist:
- partner idempotency key
- Privy `transaction_id` / user operation hash (if returned)
- final onchain `transaction_hash`
- execution path (`controller_v1` or `coordinator_v2`)

## End-to-End Partner Flow

### 1) Resolve or link the user

Call `POST /partner/v1/users/resolve` from your backend.

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/users/resolve \
  --header 'Content-Type: application/json' \
  --header 'X-Partner-Key: <PARTNER_API_KEY>' \
  --data '{
    "userId": "partner-user-123",
    "walletAddress": "0xabc...",
    "walletLabel": "Privy Embedded Wallet"
  }'
```

### 2) Create a scoped partner session

Call `POST /partner/v1/sessions`.

Use the smallest scope set needed for the current operation.

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/sessions \
  --header 'Content-Type: application/json' \
  --header 'X-Partner-Key: <PARTNER_API_KEY>' \
  --data '{
    "userId": "partner-user-123",
    "walletAddress": "0xabc...",
    "scopes": ["deposits.read", "withdrawals.write"],
    "ttlSeconds": 900
  }'
```

### 3) Run core Machines flows

Use the partner session token for:
- KYC
- Agreements
- Cards
- Balances
- Deposits

This guide focuses on withdrawals because that is where embedded-wallet server signing is most relevant.

### 4) Withdrawal flow (partner + Privy execution)

Call sequence:
1. `GET /partner/v1/withdrawals/assets`
2. `POST /partner/v1/withdrawals/range`
3. `POST /partner/v1/withdrawals/estimate`
4. `POST /partner/v1/withdrawals` (include `adminAddress`)
5. Execute onchain via Privy wallet APIs

Create request example:

```bash
curl --request POST \
  --url https://api.machines.cash/partner/v1/withdrawals \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <PARTNER_SESSION_TOKEN>' \
  --header 'Idempotency-Key: wdrl-001' \
  --data '{
    "amountCents": 2500,
    "source": {
      "contractId": "optional-uuid"
    },
    "destination": {
      "currency": "hbar",
      "network": "hbar",
      "address": "0.0.123456",
      "extraId": "optional-tag"
    },
    "adminAddress": "0xabc..."
  }'
```

Use the same `Idempotency-Key` when retrying `POST /partner/v1/withdrawals` after pending responses.

## Execution Model and Contract Call Paths

Machines withdrawal create response includes:
- `execution.callTarget`
- `execution.callPath` (`controller_v1` or `coordinator_v2`)
- `parameters` (7-arg Rain executor payload)

Always send the transaction to `execution.callTarget`.

- `controller_v1`:
  - Call 7-arg `withdrawAsset(...)`
  - Selector: `0xe167d26a`
  - Signature: `withdrawAsset(address,address,uint256,address,uint256,bytes32,bytes)`
- `coordinator_v2`:
  - Build admin typed-data signature first
  - Call 10-arg `withdrawAsset(...)`
  - Selector: `0x4b268241`
  - Signature: `withdrawAsset(address,address,uint256,address,uint256,bytes32,bytes,bytes32[],bytes[],bool)`

### v2 Typed Data Shape

```ts
const domain = {
  name: "Collateral",
  version: "2",
  chainId,
  verifyingContract: collateralProxy,
  salt: adminSalt, // bytes32
};

const types = {
  Withdraw: [
    { name: "user", type: "address" },
    { name: "asset", type: "address" },
    { name: "amount", type: "uint256" },
    { name: "recipient", type: "address" },
    { name: "nonce", type: "uint256" },
  ],
};

const message = {
  user: adminAddress,
  asset: tokenAddress,
  amount,
  recipient,
  nonce: adminNonce,
};
```

### Partner-side Privy Execution (TypeScript)

```ts
// 1) Create withdrawal payload at Machines
const wd = await fetch("https://api.machines.cash/partner/v1/withdrawals", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${partnerSessionToken}`,
    "Content-Type": "application/json",
    "Idempotency-Key": idempotencyKey,
  },
  body: JSON.stringify({
    amountCents,
    destination,
    adminAddress,
  }),
}).then((r) => r.json());

if (wd?.data?.status === "pending") {
  // retry same request with same idempotency key
}

const execution = wd?.data?.execution;
const parameters = wd?.data?.parameters;
if (!execution?.callTarget || !parameters || parameters.length < 7) {
  throw new Error("withdrawal payload not ready");
}
```

```ts
// 2) v2 only: sign typed data with Privy
const adminSignature = await privy.walletApi.ethereum.signTypedData({
  walletId,
  chainType: "ethereum",
  typedData: {
    domain,
    types,
    message,
    primaryType: "Withdraw",
  },
});
```

```ts
// 3) Send tx with Privy (v1 or v2 encoded calldata)
const send = await privy.walletApi.rpc({
  walletId,
  chainType: "ethereum",
  caip2: `eip155:${chainId}`,
  method: "eth_sendTransaction",
  sponsor: true,
  idempotencyKey,
  params: {
    transaction: {
      from: adminAddress,
      to: execution.callTarget,
      data: encodedCallData,
      value: "0x0",
    },
  },
});

const transactionId = send?.data?.transactionId;
let txHash = send?.data?.hash;
if (!txHash && transactionId) {
  // poll Privy getTransaction(transactionId) until hash is available
}
```

## Privy Method Allowances and Policy Design

For this flow, allow at minimum:
- `eth_sendTransaction`
- `eth_signTypedData_v4` (needed for `coordinator_v2`)

Use dynamic target address rules:
- Do not hardcode a single contract address.
- Restrict `to` to the `execution.callTarget` values returned by Machines for supported chains/contracts.

Recommended condition dimensions:
- `ethereum_transaction`:
  - `chain_id` in allowed chains
  - `to` in your dynamically maintained allowlist
  - `value == 0`
- `ethereum_calldata`:
  - ABI for Rain withdrawal functions
  - `function_name == "withdrawAsset"`
- `ethereum_typed_data_domain` and `ethereum_typed_data_message` (for v2):
  - domain name/version chain checks
  - typed message fields constrained to your expected flow

Policy modeling tip:
- Keep reusable condition sets per chain + function family.
- Compose those sets into authorization policies instead of duplicating large JSON blocks.

### Example Policy Shape (Ethereum)

This is a concrete starting shape aligned with Privy’s Ethereum policy examples:

```js
{
  version: "1.0",
  name: "Machines withdrawal execution",
  chain_type: "ethereum",
  rules: [
    {
      name: "Allow withdrawAsset tx to approved call targets",
      method: "eth_sendTransaction",
      action: "ALLOW",
      conditions: [
        {
          field_source: "ethereum_transaction",
          field: "chain_id",
          operator: "in",
          value: ["8453", "84532"]
        },
        {
          field_source: "ethereum_transaction",
          field: "to",
          operator: "in_condition_set",
          value: "<CALL_TARGET_CONDITION_SET_ID>"
        },
        {
          field_source: "ethereum_transaction",
          field: "value",
          operator: "eq",
          value: "0x0"
        },
        {
          field_source: "ethereum_calldata",
          field: "function_name",
          abi: [
            {
              name: "withdrawAsset",
              type: "function",
              stateMutability: "nonpayable",
              inputs: [
                { name: "collateralProxy", type: "address" },
                { name: "token", type: "address" },
                { name: "amount", type: "uint256" },
                { name: "recipient", type: "address" },
                { name: "expiresAt", type: "uint256" },
                { name: "executorSalt", type: "bytes32" },
                { name: "executorSignature", type: "bytes" }
              ],
              outputs: []
            },
            {
              name: "withdrawAsset",
              type: "function",
              stateMutability: "nonpayable",
              inputs: [
                { name: "collateralProxy", type: "address" },
                { name: "token", type: "address" },
                { name: "amount", type: "uint256" },
                { name: "recipient", type: "address" },
                { name: "expiresAt", type: "uint256" },
                { name: "executorSalt", type: "bytes32" },
                { name: "executorSignature", type: "bytes" },
                { name: "adminSalt", type: "bytes32[]" },
                { name: "adminSignature", type: "bytes[]" },
                { name: "directTransfer", type: "bool" }
              ],
              outputs: []
            }
          ],
          operator: "eq",
          value: "withdrawAsset"
        }
      ]
    },
    {
      name: "Allow v2 domain-constrained typed data signing",
      method: "eth_signTypedData_v4",
      action: "ALLOW",
      conditions: [
        {
          field_source: "ethereum_typed_data_domain",
          field: "chainId",
          operator: "in",
          value: ["8453", "84532"]
        },
        {
          field_source: "ethereum_typed_data_domain",
          field: "verifyingContract",
          operator: "in_condition_set",
          value: "<COLLATERAL_PROXY_CONDITION_SET_ID>"
        },
        {
          field_source: "ethereum_typed_data_message",
          typed_data: {
            types: {
              Withdraw: [
                { name: "user", type: "address" },
                { name: "asset", type: "address" },
                { name: "amount", type: "uint256" },
                { name: "recipient", type: "address" },
                { name: "nonce", type: "uint256" }
              ]
            },
            primary_type: "Withdraw"
          },
          field: "amount",
          operator: "lte",
          value: "<HEX_MAX_ALLOWED_AMOUNT>"
        }
      ]
    }
  ]
}
```

Practical guidance:
- Use condition sets for `to` addresses and verifying contracts.
- Keep policy values as strings matching Privy examples (`chain_id`, hex values, address strings).
- Treat this policy as an allowlist; add explicit `DENY` rules only when needed.

## Gas Sponsorship and Transaction Lifecycle

- Use `sponsor: true` for embedded wallet sends to sponsor gas.
- For gas-sponsored EVM sends, backend responses can return before final onchain hash.
- Handle async status fields:
  - `transaction_id` (Privy transaction id)
  - `user_operation_hash`
  - `hash` may be empty until confirmation.
- Track final status via:
  - Privy webhooks (recommended)
  - or transaction-status API polling by `transaction_id`.
- Add operational monitoring:
  - submission failures
  - hash timeouts
  - onchain confirmation delays
  - spend anomalies and abuse patterns

## Known Constraints and Gotchas

- Production destination coverage (BTC/SOL/EVM/etc.) depends on live relay routes. Always query:
  - `GET /partner/v1/withdrawals/assets`
  - `POST /partner/v1/withdrawals/range`
  - `POST /partner/v1/withdrawals/estimate`
  before quoting or creating withdrawals.
- Sandbox source is fixed to rUSD on Base Sepolia.
- If withdrawal signature response is `status: pending`, retry with the same idempotency key.

## Further Reading (Privy)

- Authorization keys overview:
  - https://docs.privy.io/controls/authorization-keys
- Server-side signing with authorization keys:
  - https://docs.privy.io/controls/authorization-keys/using-owners/sign/signing-on-the-server
- Ethereum policy examples:
  - https://docs.privy.io/controls/policies/example-policies/ethereum#allow-specific-smart-contract-function-calls
- Gas sponsorship setup:
  - https://docs.privy.io/wallets/gas-and-asset-management/gas/setup
- Transaction handling:
  - https://docs.privy.io/wallets/gas-and-asset-management/gas/transaction-handling
- Sponsorship security guidance:
  - https://docs.privy.io/wallets/gas-and-asset-management/gas/security
````
#### File: kyc-flow.mdx
````mdx
---
title: "KYC Flow"
description: "Collect and verify identity."
---

<Tip>Prerequisite: have a session token. See Authentication.</Tip>

## Flow
```mermaid
sequenceDiagram
  participant Backend as Your Backend
  participant MachinesAPI as Machines API

  Backend->>MachinesAPI: POST /sessions (create session)
  Backend->>MachinesAPI: GET /kyc/values
  Backend->>MachinesAPI: POST /kyc/applications
  MachinesAPI-->>Backend: status = pending
  loop poll
    Backend->>MachinesAPI: GET /kyc/status
    MachinesAPI-->>Backend: status = approved | needs_information | denied
  end
  Backend->>MachinesAPI: GET /agreements
  Backend->>MachinesAPI: POST /agreements
```

## Steps
<Steps>
  <Step title="1) Get fields + values">
    <a href="/api/kyc/values" target="_blank" rel="noreferrer"><code>GET /kyc/values</code></a> to see required fields and allowed values.
  </Step>
  <Step title="2) Submit application">
    <a href="/api/kyc/applications" target="_blank" rel="noreferrer"><code>POST /kyc/applications</code></a> with the fields below. Sandbox: set <code>lastName</code> to <code>approved</code> to auto‑approve.
  </Step>
  <Step title="3) Complete verification">
    We return a <code>completionLink</code> (or <code>externalVerificationLink</code>). Open it in a browser for document upload and face match. Users finish verification there — you don’t need to handle media.
  </Step>
  <Step title="4) Check status">
    Poll <a href="/api/kyc/status" target="_blank" rel="noreferrer"><code>GET /kyc/status</code></a> until the status becomes <code>approved</code> (or an actionable state such as <code>needs_information</code>).
  </Step>
  <Step title="5) Accept agreements">
    After approval, fetch the agreements and collect user consent. See <a href="/agreements">Agreements</a>.
  </Step>
</Steps>

## States
- approved
- pending
- needs_information
- needs_verification
- manual_review
- denied
- locked
- canceled

## Fields (submit application)
<ParamField path="firstName" type="string" required>
  Given name.
</ParamField>
<ParamField path="lastName" type="string" required>
  Family name. Sandbox shortcut: set to <code>approved</code> to auto-approve.
</ParamField>
<ParamField path="birthDate" type="string" required>
  YYYY-MM-DD (e.g., 1990-01-01).
</ParamField>
<ParamField path="nationalId" type="string" required>
  Government ID number (letters, numbers, dashes only).
</ParamField>
<ParamField path="countryOfIssue" type="string" required>
  ISO-3166-1 alpha-2 country code (e.g., US). Case-insensitive.
</ParamField>
<ParamField path="email" type="string" required>
  Email address.
</ParamField>
<ParamField path="phoneCountryCode" type="string">
  Optional phone dial code digits only (e.g., 1).
</ParamField>
<ParamField path="phoneNumber" type="string">
  Optional phone number digits only (include area code).
</ParamField>
<ParamField path="address.line1" type="string" required>
  Street address (line 1).
</ParamField>
<ParamField path="address.line2" type="string">
  Street address (line 2).
</ParamField>
<ParamField path="address.city" type="string" required>
  City.
</ParamField>
<ParamField path="address.region" type="string" required>
  State/region (e.g., CA).
</ParamField>
<ParamField path="address.postalCode" type="string" required>
  Postal/ZIP code.
</ParamField>
<ParamField path="address.countryCode" type="string" required>
  ISO-3166-1 alpha-2 country code (e.g., US). Case-insensitive.
</ParamField>
<ParamField path="occupation" type="string" required>
  SOC occupation code (e.g., <code>49-3023</code>). Full list: <a href="/kyc-values">KYC Field Values</a>.
</ParamField>
<ParamField path="annualSalary" type="string" required>
  Choose one label (we map to the underlying range for you):
  - <code>&lt;40k</code>
  - <code>50k–99k</code>
  - <code>100k–149k</code>
  - <code>150k+</code>
</ParamField>
<ParamField path="accountPurpose" type="string" required>
  Choose one:
  - <code>everyday spend</code>
  - <code>subscriptions</code>
  - <code>business expenses</code>
  - <code>testing</code>
  - <code>other</code>
</ParamField>
<ParamField path="expectedMonthlyVolume" type="string" required>
  Choose one label (we map to the underlying range for you):
  - <code>under $1k</code>
  - <code>$1k–$5k</code>
  - <code>$5k–$20k</code>
  - <code>$20k+</code>
</ParamField>
## Response (status)
<ResponseField name="status" type="string" required>
  One of: <code>approved</code>, <code>pending</code>, <code>needs_information</code>, <code>needs_verification</code>, <code>manual_review</code>, <code>denied</code>, <code>locked</code>, <code>canceled</code>.
</ResponseField>
<ResponseField name="applicationId" type="string">
  Identifier for the submitted application (when available).
</ResponseField>
<ResponseField name="reason" type="string">
  Additional context for non-approved statuses.
</ResponseField>

<Note>Sandbox shortcut: set <code>lastName</code> to <code>approved</code> to auto‑approve. See <a href="/kyc-values">KYC Field Values</a> for allowed field values.</Note>
````

#### File: agreements.mdx
````mdx
---
title: "Agreements"
description: "Display required user agreements after KYC approval."
---

<Tip>Prerequisite: KYC status is <code>approved</code>.</Tip>

## Flow
```mermaid
sequenceDiagram
  participant Backend as Your Backend
  participant MachinesAPI as Machines API
  participant User as User

  Backend->>MachinesAPI: GET /agreements
  MachinesAPI-->>Backend: agreements + accepted status
  Backend->>User: show agreements + links
  User-->>Backend: accepts all
  Backend->>MachinesAPI: POST /agreements { accepted: true }
  MachinesAPI-->>Backend: acceptedAt
```

## API flows (step-by-step)
<Steps>
  <Step title="1) Fetch agreements">
    <b>Endpoint</b>: <a href="/api/agreements/list" target="_blank" rel="noreferrer"><code>GET /agreements</code></a><br/>
    <b>Response</b>: <code>agreements[]</code> (text + links), <code>accepted</code>, <code>acceptedAt</code>.

```bash
curl -s \
  -H 'Authorization: Bearer <SESSION_TOKEN>' \
  https://dev-api.machines.cash/partner/v1/agreements
```
  </Step>
  <Step title="2) Show agreements to the user">
    Render each agreement’s <code>text</code>, and open the <code>links[].url</code> in a new tab.
    The user must accept all agreements.
  </Step>
  <Step title="3) Submit acceptance">
    <b>Endpoint</b>: <a href="/api/agreements/accept" target="_blank" rel="noreferrer"><code>POST /agreements</code></a><br/>
    <b>Body</b>: <code>{"{ \"accepted\": true }"}</code>

```bash
curl -s \
  -H 'Authorization: Bearer <SESSION_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{"accepted": true}' \
  https://dev-api.machines.cash/partner/v1/agreements
```
  </Step>
</Steps>

<Note>If KYC is not approved, these endpoints return <code>409</code> with <code>invalid_state</code>.</Note>

## What this unlocks
After acceptance, the user can create cards, create deposits, and request withdrawals. Those endpoints return <code>409</code> until agreements are accepted.
````

#### File: cards.mdx
````mdx
---
title: "Cards"
description: "Create, lock/unlock, delete, and retrieve encrypted card secrets."
---

<Tip>Prerequisite: have a session token. See Authentication.</Tip>

## Card lifecycle
```mermaid
sequenceDiagram
  participant Backend as Your Backend
  participant MachinesAPI as Machines API

  Backend->>MachinesAPI: POST /cards
  MachinesAPI-->>Backend: cardId
  Backend->>MachinesAPI: PATCH /cards/{"{cardId}"} (lock/unlock, limit)
  MachinesAPI-->>Backend: updated card
  Backend->>MachinesAPI: DELETE /cards/{"{cardId}"}
  MachinesAPI-->>Backend: canceled
```

## API flows (step‑by‑step)
<Steps>
  <Step title="List cards">
    <b>Endpoint</b>: <a href="/api/cards/list" target="_blank" rel="noreferrer"><code>GET /cards</code></a>
  </Step>
  <Step title="Create a card">
    <b>Endpoint</b>: <a href="/api/cards/create" target="_blank" rel="noreferrer"><code>POST /cards</code></a><br/>
    <b>Body</b>: optional <code>encryptedName</code> (encrypted card label) and optional <code>limit</code> with <code>amountCents</code> and <code>frequency</code>.
    <br/>
    <b>Tip</b>: To set a card label, call <a href="/api/encryption/encrypt" target="_blank" rel="noreferrer"><code>POST /encryption/encrypt</code></a> first and pass the returned <code>value</code> as <code>encryptedName</code> (see <a href="/encryption">Encryption</a>).

```bash
curl -s \
  -H 'Authorization: Bearer <SESSION_TOKEN>' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <UUID>' \
  -d '{"limit": {"amountCents": 2500, "frequency": "perAuthorization"}}' \
  https://dev-api.machines.cash/partner/v1/cards
```
  </Step>
  <Step title="Update status or limits">
    <b>Endpoint</b>: <a href="/api/cards/update" target="_blank" rel="noreferrer"><code>PATCH /cards/{"{cardId}"}</code></a>
  </Step>
  <Step title="Delete a card">
    <b>Endpoint</b>: <a href="/api/cards/delete" target="_blank" rel="noreferrer"><code>DELETE /cards/{"{cardId}"}</code></a>
  </Step>
  <Step title="Create a card secrets session (optional)">
    <b>Endpoint</b>: <a href="/api/cards/secrets/session" target="_blank" rel="noreferrer"><code>POST /cards/secrets/session</code></a>
    <br/>
    <b>Body</b>: send an empty JSON object if your client sets <code>Content-Type: application/json</code>.

```bash
curl -s \
  -H 'Authorization: Bearer <SESSION_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{}' \
  https://dev-api.machines.cash/partner/v1/cards/secrets/session
```
  </Step>
  <Step title="Get card secrets (optional)">
    <b>Endpoint</b>: <a href="/api/cards/secrets" target="_blank" rel="noreferrer"><code>POST /cards/{"{cardId}"}/secrets</code></a><br/>
    <b>Body</b>: <code>{"{ sessionId }"}</code> (from the secrets session helper)
  </Step>
</Steps>

## Fields (create/update)
<ParamField path="encryptedName" type="object">
  Optional encrypted card label field <code>{"{ v, iv, ct }"}</code>. Generate with <a href="/api/encryption/encrypt" target="_blank" rel="noreferrer"><code>POST /encryption/encrypt</code></a>.
</ParamField>
<ParamField path="limit.amountCents" type="integer">
  Spending cap in cents.
</ParamField>
<ParamField path="limit.frequency" type="string">
  One of: <code>perAuthorization</code>, <code>per24HourPeriod</code>, <code>per7DayPeriod</code>, <code>per30DayPeriod</code>, <code>perYearPeriod</code>, <code>allTime</code>.
</ParamField>

## Response (secrets session)
<ResponseField name="sessionId" type="string" required>
  Session id used to fetch encrypted PAN/CVC.
</ResponseField>
<ResponseField name="secretKey" type="string" required>
  16‑byte secret (hex) used to decrypt PAN/CVC with AES‑128‑GCM.
</ResponseField>

## Response (secrets)
<ResponseField name="encryptedPan.data" type="string" required>
  Base64 ciphertext + auth tag.
</ResponseField>
<ResponseField name="encryptedPan.iv" type="string" required>
  Base64 IV.
</ResponseField>
<ResponseField name="encryptedCvc.data" type="string" required>
  Base64 ciphertext + auth tag.
</ResponseField>
<ResponseField name="encryptedCvc.iv" type="string" required>
  Base64 IV.
</ResponseField>
<ResponseField name="expirationMonth" type="number" required>
  Expiration month (MM).
</ResponseField>
<ResponseField name="expirationYear" type="number" required>
  Expiration year (YYYY).
</ResponseField>
<ResponseField name="last4" type="string" required>
  Last 4 digits.
</ResponseField>

## Card labels vs card secrets
- Card labels use <code>/encryption/encrypt</code> and <code>/encryption/decrypt</code>.
- Card secrets (PAN/CVC) use <code>/cards/secrets/session</code> + <code>/cards/{"{cardId}"}/secrets</code>.
- Decrypt card labels on your backend and send plaintext labels to your frontend.
- Card labels are optional, but recommended for clean UI.

## Card limits
- amountCents: integer amount in cents (e.g., 5000 = $50). Minimum 1.
- frequency: one of
  - <code>perAuthorization</code> — max per single authorization
  - <code>per24HourPeriod</code> — rolling 24‑hour spend cap
  - <code>per7DayPeriod</code> — rolling 7‑day spend cap
  - <code>per30DayPeriod</code> — rolling 30‑day spend cap
  - <code>perYearPeriod</code> — rolling 1‑year cap
  - <code>allTime</code> — lifetime cap

Examples
```json
{ "limit": { "amountCents": 5000, "frequency": "perAuthorization" } }
{ "limit": { "amountCents": 20000, "frequency": "per24HourPeriod" } }
```

To change a limit, PATCH the card with a new limit object. To remove a limit, set a permissive value (for example a very high allTime cap) or contact us if you need a true “no limit” configuration for your program.

## Lock / unlock / update limits
- <code>PATCH /cards/{"{cardId}"}</code> with <code>status: "locked"</code> to pause spend.
- Set <code>status: "active"</code> to unlock.
- Update limits by sending a new <code>limit</code> payload.

## Delete
<code>DELETE /cards/{"{cardId}"}</code> cancels immediately.

<a id="secrets-sessionid"></a>
## Card number secrets (PAN/CVC)
This flow is only for retrieving the card number (PAN) and CVC. Do not use it for card labels.

1) Call <code>POST /cards/secrets/session</code> to get <code>sessionId</code> and <code>secretKey</code>.
2) Call <code>POST /cards/{"{cardId}"}/secrets</code> with <code>{"{ sessionId }"}</code>.
3) Decrypt <code>encryptedPan</code> and <code>encryptedCvc</code> locally using AES‑128‑GCM with the <code>secretKey</code>.

<Note>This endpoint requires the <code>cards.secrets.read</code> scope.</Note>
<Note><code>/encryption/decrypt</code> is for card labels only. Use <code>/cards/{"{cardId}"}/secrets</code> for PAN/CVC.</Note>
<Note>The <code>sessionId</code> used here is different from your API session token. Always request it from <code>/cards/secrets/session</code>.</Note>
<Note>No decrypted PAN/CVC ever leaves your process; only encrypted blobs are returned by the API.</Note>
````

#### File: balances.mdx
````mdx
---
title: "Balances"
description: "Fetch spending power and charge totals."
---


## Overview
- <code>GET /balances</code> returns credit limit, spending power, and charge totals (all in cents).
- No extra scopes are required.

## API flow (step‑by‑step)
<Steps>
  <Step title="Fetch balances">
    <b>Endpoint</b>: <a href="/api/balances/get" target="_blank" rel="noreferrer"><code>GET /balances</code></a><br/>
    <b>Headers</b>: <code>Authorization: Bearer &lt;SESSION_TOKEN&gt;</code>

```bash
curl -s \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  https://dev-api.machines.cash/partner/v1/balances
```

Use the returned values to display available spend and current charge totals.
  </Step>
</Steps>

## Response (balances)
<ResponseField name="balances.spendingPower" type="number">
  Available spending power in cents.
</ResponseField>
<ResponseField name="balances.creditLimit" type="number">
  Credit limit in cents.
</ResponseField>
<ResponseField name="balances.pendingCharges" type="number">
  Pending charges in cents.
</ResponseField>
<ResponseField name="balances.postedCharges" type="number">
  Posted charges in cents.
</ResponseField>
<ResponseField name="balances.balanceDue" type="number">
  Balance due in cents.
</ResponseField>
````

#### File: deposits.mdx
````mdx
---
title: "Deposits"
description: "Partner deposit discovery, range validation, estimation, and create flow."
---

<Note>Sandbox supports partner deposits only for `rusd` on `base` (Base Sepolia).</Note>

## Overview
- `GET /partner/v1/deposits/assets`: discover supported `currency` + `network` ids (no min/max).
- `POST /partner/v1/deposits/range`: fetch min/max and payout route for one pair.
- `POST /partner/v1/deposits/estimate`: quote estimated receive amount for one pair + amount.
- `POST /partner/v1/deposits`: create the deposit address/intent.
- `GET /partner/v1/deposits` and `GET /partner/v1/deposits/{depositId}`: track lifecycle.

<Note>Min/max are intentionally omitted from assets. Always call `/deposits/range` (or `/deposits/estimate`) for limits.</Note>

## Flow
```mermaid
sequenceDiagram
  participant Backend as "Your Backend"
  participant MachinesAPI as "Machines API"
  participant Wallet as "Wallet / Exchange"

  Backend->>MachinesAPI: GET /partner/v1/deposits/assets
  MachinesAPI-->>Backend: assets[]
  Backend->>MachinesAPI: POST /partner/v1/deposits/range
  MachinesAPI-->>Backend: range {minAmount, maxAmount, route}
  Backend->>MachinesAPI: POST /partner/v1/deposits/estimate
  MachinesAPI-->>Backend: estimate {estimatedToAmount, rateId}
  Backend->>MachinesAPI: POST /partner/v1/deposits
  MachinesAPI-->>Backend: deposit {depositAddress, payinExtraId}
  Wallet->>MachinesAPI: Send funds to depositAddress (+ memo/tag when payinExtraId exists)
  Backend->>MachinesAPI: GET /partner/v1/deposits/{depositId}
  MachinesAPI-->>Backend: status updates
```

## API flows (step-by-step)
<Steps>
  <Step title="1) Discover deposit assets">
    <b>Endpoint</b>: <a href="/api/deposits/assets" target="_blank" rel="noreferrer"><code>GET /partner/v1/deposits/assets</code></a>

```bash
curl -s \
  -H "Authorization: Bearer $PARTNER_SESSION_TOKEN" \
  "https://api.machines.cash/partner/v1/deposits/assets?q=hbar&limit=20"
```

Response returns `assets[].ticker` and `assets[].networks[].id`.
  </Step>

  <Step title="2) Get range for selected pair">
    <b>Endpoint</b>: <a href="/api/deposits/range" target="_blank" rel="noreferrer"><code>POST /partner/v1/deposits/range</code></a>

```bash
curl -s \
  -H "Authorization: Bearer $PARTNER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"currency":"hbar","network":"hbar"}' \
  https://api.machines.cash/partner/v1/deposits/range
```

Use `range.minAmount` / `range.maxAmount` for validation.
  </Step>

  <Step title="3) Estimate receive amount">
    <b>Endpoint</b>: <a href="/api/deposits/estimate" target="_blank" rel="noreferrer"><code>POST /partner/v1/deposits/estimate</code></a>

```bash
curl -s \
  -H "Authorization: Bearer $PARTNER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"currency":"hbar","network":"hbar","amount":25,"amountCurrency":"crypto"}' \
  https://api.machines.cash/partner/v1/deposits/estimate
```

`amountCurrency` supports:
- `crypto` (default)
- `usd`
  </Step>

  <Step title="4) Create deposit intent">
    <b>Endpoint</b>: <a href="/api/deposits/create" target="_blank" rel="noreferrer"><code>POST /partner/v1/deposits</code></a>

```bash
curl -s \
  -H "Authorization: Bearer $PARTNER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"currency":"hbar","network":"hbar","amount":25}' \
  https://api.machines.cash/partner/v1/deposits
```

Use the returned `deposit.depositAddress`. If `deposit.payinExtraId` is non-null, pass it as memo/tag in the user’s funding transaction.
  </Step>

  <Step title="5) Track status">
    <b>Endpoints</b>:
    - <a href="/api/deposits/get" target="_blank" rel="noreferrer"><code>GET /partner/v1/deposits/{depositId}</code></a>
    - <a href="/api/deposits/list" target="_blank" rel="noreferrer"><code>GET /partner/v1/deposits?scope=active</code></a>
  </Step>
</Steps>

## Key response fields
<ResponseField name="deposit.payinExtraId" type="string">
  Optional memo/tag required by some source networks.
</ResponseField>
<ResponseField name="estimate.quotedAmountCurrency" type="string" required>
  Quote input mode (`crypto` or `usd`).
</ResponseField>
<ResponseField name="estimate.estimatedToAmount" type="number">
  Estimated settled amount for the user contract route.
</ResponseField>
<ResponseField name="estimate.rateId" type="string">
  Provider rate id when available.
</ResponseField>
````

#### File: withdrawals.mdx
````mdx
---
title: "Withdrawals"
description: "Partner withdrawal discovery, range validation, estimation, relay setup, and onchain execution."
---

<Tip>Read endpoints for withdrawals use `deposits.read` scope. Create still requires `withdrawals.write`.</Tip>

## Overview
- `GET /partner/v1/withdrawals/assets`: list destination assets.
- `POST /partner/v1/withdrawals/range`: get min/max for a destination pair.
- `POST /partner/v1/withdrawals/estimate`: quote destination receive amount for a given amount.
- `POST /partner/v1/withdrawals`: create relay + fetch Rain withdrawal signature payload.

<Warning>`POST /partner/v1/withdrawals` uses a breaking request body shape with `source` and `destination` objects.</Warning>

## Flow
```mermaid
sequenceDiagram
  participant Backend as "Your Backend"
  participant MachinesAPI as "Machines API"
  participant Rain as "Rain Contract"
  participant ChangeNOW as "ChangeNOW Relay"
  participant Chain as "Blockchain"

  Backend->>MachinesAPI: GET /partner/v1/withdrawals/assets
  MachinesAPI-->>Backend: assets[]
  Backend->>MachinesAPI: POST /partner/v1/withdrawals/range
  MachinesAPI-->>Backend: range {min/max}
  Backend->>MachinesAPI: POST /partner/v1/withdrawals/estimate
  MachinesAPI-->>Backend: estimate {estimatedToAmount}
  Backend->>MachinesAPI: POST /partner/v1/withdrawals
  MachinesAPI->>ChangeNOW: create exchange (pay-in = Rain recipient)
  MachinesAPI->>Rain: get signed withdrawal payload
  MachinesAPI-->>Backend: status + signature + relay metadata
  Backend->>Chain: sign/broadcast withdrawAsset(...)
```

## API flows (step-by-step)
<Steps>
  <Step title="1) Discover destination assets">
    <b>Endpoint</b>: <a href="/api/withdrawals/assets" target="_blank" rel="noreferrer"><code>GET /partner/v1/withdrawals/assets</code></a>

```bash
curl -s \
  -H "Authorization: Bearer $PARTNER_SESSION_TOKEN" \
  "https://api.machines.cash/partner/v1/withdrawals/assets?q=hbar&limit=20"
```

`sourceChainId` and `sourceTokenAddress` are optional hints.
  </Step>

  <Step title="2) Get min/max for chosen route">
    <b>Endpoint</b>: <a href="/api/withdrawals/range" target="_blank" rel="noreferrer"><code>POST /partner/v1/withdrawals/range</code></a>

```bash
curl -s \
  -H "Authorization: Bearer $PARTNER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": { "currency": "hbar", "network": "hbar" }
  }' \
  https://api.machines.cash/partner/v1/withdrawals/range
```
  </Step>

  <Step title="3) Estimate destination receive amount">
    <b>Endpoint</b>: <a href="/api/withdrawals/estimate" target="_blank" rel="noreferrer"><code>POST /partner/v1/withdrawals/estimate</code></a>

```bash
curl -s \
  -H "Authorization: Bearer $PARTNER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": { "currency": "hbar", "network": "hbar", "extraId": "optional-tag" },
    "amountCents": 2500
  }' \
  https://api.machines.cash/partner/v1/withdrawals/estimate
```
  </Step>

  <Step title="4) Create withdrawal signature payload (breaking schema)">
    <b>Endpoint</b>: <a href="/api/withdrawals/create" target="_blank" rel="noreferrer"><code>POST /partner/v1/withdrawals</code></a>

```bash
curl -s \
  -H "Authorization: Bearer $PARTNER_SESSION_TOKEN" \
  -H "Idempotency-Key: wdrl-123456" \
  -H "Content-Type: application/json" \
  -d '{
    "amountCents": 2500,
    "source": {
      "contractId": "optional-uuid"
    },
    "destination": {
      "currency": "hbar",
      "network": "hbar",
      "address": "0.0.123456",
      "extraId": "optional-tag"
    },
    "adminAddress": "0x..."
  }' \
  https://api.machines.cash/partner/v1/withdrawals
```

Ready responses include:
- `execution` info for contract call routing.
- `relay` metadata (`payinAddress`, `payinExtraId`, `payoutAddress`, `payoutExtraId`, route ids).
  </Step>

  <Step title="5) Execute onchain">
    - `from` must be the same admin EOA used in create (`adminAddress`).
    - `to` must be `execution.callTarget` (controller/coordinator), not the collateral proxy.
    - Use ordered args from `parameters`: `[collateralProxy, token, amount, recipient, expiresAt, executorSalt, executorSignature]`.
    - If `execution.callPath=controller_v1`, call 7-arg `withdrawAsset(...)`.
    - If `execution.callPath=coordinator_v2`, call 10-arg `withdrawAsset(...)` and include v2 admin typed-data signature values.
    - If `status` is `pending`, wait `retryAfterSeconds` and retry the same request with the same idempotency key.
  </Step>
</Steps>

## Relay metadata fields
<ResponseField name="relay.changeNowId" type="string">
  Provider exchange id for relay tracking.
</ResponseField>
<ResponseField name="relay.payinAddress" type="string">
  Address Rain withdrawal sends funds to.
</ResponseField>
<ResponseField name="relay.payinExtraId" type="string">
  Optional memo/tag required on relay pay-in side.
</ResponseField>
<ResponseField name="relay.payoutAddress" type="string">
  Final destination address configured on the relay.
</ResponseField>
<ResponseField name="relay.payoutExtraId" type="string">
  Optional destination memo/tag configured for payout.
</ResponseField>
````

#### File: agentic-payments.mdx
````mdx
---
title: "Agentic Payments"
description: "Build reliable, safe-by-construction agent flows for card issuance and spending."
---

## Principles for Agents
- Narrow, explicit schemas (JSON Schema) to remove ambiguity.
- Idempotency where supported (use <code>Idempotency-Key</code> on card creation).
- Strict, deterministic error codes; cursor pagination; search + get-by-id pairs.

## Common Flows
### Create Fixed-Limit Card (single step)
1) Optional: <code>POST /encryption/encrypt</code> with the card label, then <code>POST /cards</code> with <code>encryptedName</code>. You can also omit <code>encryptedName</code> and set it later.
2) Include <code>limit.amountCents</code> and <code>frequency</code> if you want a fixed limit.
3) Call <code>POST /cards/secrets/session</code>, then <code>POST /cards/{cardId}/secrets</code> to fetch encrypted PAN/CVC.
4) Optional: `DELETE /cards/{cardId}` to clean up.

### Lock / Unlock
1) <code>PATCH /cards/{cardId}</code> with <code>{"{ status: \"locked\" }"}</code>.
2) Resume with <code>{"{ status: \"active\" }"}</code>.

## Open Responses compatibility (optional)
Set <code>X-Open-Responses: 1</code> on any partner endpoint to receive an Open Responses-style response object. If you already have a tool call id, pass <code>X-Open-Responses-Call-Id</code> so the response includes a <code>function_call_output</code> item with a matching <code>call_id</code>. If omitted, a call id is generated.

The response always includes:
- A <code>message</code> item containing the <code>summary</code>.
- A <code>function_call_output</code> item containing the full partner envelope as a JSON string.

```bash
curl -s \
  -H 'X-Open-Responses: 1' \
  -H 'X-Open-Responses-Call-Id: call_123' \
  https://dev-api.machines.cash/partner/v1/cards
```

```json
{
  "id": "resp_...",
  "object": "response",
  "status": "completed",
  "model": "machinescash.partner",
  "output": [
    {
      "type": "message",
      "role": "assistant",
      "content": [
        {
          "type": "output_text",
          "text": "no cards",
          "annotations": [],
          "logprobs": []
        }
      ]
    },
    {
      "type": "function_call_output",
      "call_id": "call_123",
      "output": "{\"ok\":true,\"data\":{\"cards\":[]},\"summary\":\"no cards\",\"errors\":[]}"
    }
  ]
}
```

<Note>Additional required fields are returned (timestamps, tool_choice, truncation, usage, etc). They are omitted here for readability. Streaming SSE is not supported on partner routes yet.</Note>

## Tool Schemas (LLM-ready)
Use verb-first, namespaced tool names and strict schemas:

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["userId", "walletAddress", "scopes"],
  "properties": {
    "userId": { "type": "string", "maxLength": 120 },
    "walletAddress": { "type": "string", "pattern": "^0x[a-fA-F0-9]{40}$" },
    "scopes": { "type": "array", "items": { "type": "string" }, "minItems": 1 }
  }
}
```

```json
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "encryptedName": { "type": "object", "required": ["v","iv","ct"], "properties": {
      "v": { "type": "integer" },
      "iv": { "type": "string" },
      "ct": { "type": "string" }
    }},
    "limit": { "type": "object", "required": ["amountCents","frequency"], "properties": {
      "amountCents": { "type": "integer", "minimum": 1 },
      "frequency": { "type": "string", "enum": ["perAuthorization","per24HourPeriod","per30DayPeriod"] }
    }}
  }
}
```

<Tip>Include helpful <code>summary</code> strings in responses for LLM chaining, and keep payloads compact.</Tip>
````

#### File: agent-tool-schemas.mdx
````mdx
---
title: "Agent Tool Schemas"
description: "Copy-paste JSON Schemas for tools to call the API."
---

<CodeGroup>

```json api.users.resolve
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["userId"],
  "properties": {
    "userId": { "type": "string", "maxLength": 120 },
    "walletAddress": { "type": "string", "pattern": "^0x[a-fA-F0-9]{40}$" },
    "walletLabel": { "type": "string", "maxLength": 120 }
  }
}
```


```json api.sessions.create
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["userId","scopes"],
  "properties": {
    "userId": { "type": "string" },
    "walletAddress": { "type": "string", "pattern": "^0x[a-fA-F0-9]{40}$" },
    "scopes": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
    "ttlSeconds": { "type": "integer", "minimum": 60, "maximum": 86400 }
  }
}
```


```json api.cards.create
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "encryptedName": { "type": "object", "required": ["v","iv","ct"], "properties": {
      "v": { "type": "integer" },
      "iv": { "type": "string" },
      "ct": { "type": "string" }
    }},
    "limit": { "type": "object", "required": ["amountCents","frequency"], "properties": {
      "amountCents": { "type": "integer", "minimum": 1 },
      "frequency": { "type": "string", "enum": ["perAuthorization","per24HourPeriod","per7DayPeriod","per30DayPeriod","perYearPeriod","allTime"] }
    }}
  }
}
```


```json api.encryption.encrypt
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["value"],
  "properties": {
    "value": { "type": "string" }
  }
}
```


```json api.balances.get
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "properties": {}
}
```


```json api.cards.update
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "status": { "type": "string", "enum": ["active","locked","canceled","not_activated"] },
    "limit": { "type": "object", "required": ["amountCents","frequency"], "properties": {
      "amountCents": { "type": "integer", "minimum": 1 },
      "frequency": { "type": "string", "enum": ["perAuthorization","per24HourPeriod","per7DayPeriod","per30DayPeriod","perYearPeriod","allTime"] }
    }},
    "encryptedName": { "type": "object", "required": ["v","iv","ct"], "properties": {
      "v": { "type": "integer" },
      "iv": { "type": "string" },
      "ct": { "type": "string" }
    }}
  }
}
```


```json api.cards.secrets.get
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["sessionId"],
  "properties": {
    "sessionId": { "type": "string" }
  }
}
```


```json api.cards.secrets.session
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "properties": {}
}
```


```json api.deposits.create
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["currency","network"],
  "properties": {
    "currency": { "type": "string", "enum": ["usdc","rusd"] },
    "network": { "type": "string", "enum": ["base"] },
    "amount": { "type": "number", "exclusiveMinimum": 0 }
  }
}
```


```json api.withdrawals.create
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["amountCents","tokenAddress","recipientAddress","chainId"],
  "properties": {
    "amountCents": { "type": "integer", "minimum": 1 },
    "tokenAddress": { "type": "string", "pattern": "^0x[a-fA-F0-9]{40}$" },
    "recipientAddress": { "type": "string", "pattern": "^0x[a-fA-F0-9]{40}$" },
    "chainId": { "type": "integer", "minimum": 1 },
    "contractId": { "type": "string" },
    "adminAddress": { "type": "string", "pattern": "^0x[a-fA-F0-9]{40}$" }
  }
}
```

</CodeGroup>

<Note>
  Set <code>
  additionalProperties: false</code>

   and explicit <code>
  required</code>

   to keep agents deterministic.
</Note>
````

#### File: best-practices-agents.mdx
````mdx
---
title: "Agent Best Practices"
description: "Guardrails and patterns that keep agent calls safe and reliable."
---

- Use <code>Idempotency-Key</code> on card creation and other idempotent writes.
- Use propose -> execute for sensitive actions.
- Return small, structured results with a <code>summary</code> for LLM chaining.
- Prefer cursor pagination and search + get-by-id pairs.
- Implement bounded retries on networking timeouts.
````

#### File: errors.mdx
````mdx
---
title: "Errors & Retries"
description: "Deterministic error codes and retry guidance for agents and services."
---

Common error codes:
- <code>invalid_request</code> (400)
- <code>unauthorized</code> (401)
- <code>forbidden</code> (403)
- <code>not_found</code> (404)
- <code>conflict</code> (409)
- <code>service_unavailable</code> (503)

<Note>All responses use a consistent envelope by default: `{ ok, data, summary, errors[], next }`. Set <code>X-Open-Responses: 1</code> to receive Open Responses-style outputs.</Note>

## Open Responses error shape (optional)
When <code>X-Open-Responses: 1</code> is set, errors return the Open Responses error object:

```json
{
  "error": {
    "type": "invalid_request",
    "code": "invalid_request",
    "param": "walletAddress",
    "message": "walletAddress required to create a new mapping"
  }
}
```
````

#### File: kyc-values.mdx
````mdx
---
title: "KYC Field Values"
description: "Expected values for occupation, salary, account purpose, and monthly volume."
---

Use these values when submitting KYC. The endpoint returns field requirements plus the allowed values, so you can build the form in one call.
Country codes use ISO-3166-1 alpha-2 (e.g., US) for <code>countryOfIssue</code> and <code>address.countryCode</code>. Case-insensitive.

<Note>API endpoint: <a href="/api/kyc/values" target="_blank" rel="noreferrer"><code>GET /kyc/values</code></a>.</Note>

The response includes a <code>fields</code> array with required/optional flags and validation hints.

## Occupations (SOC codes)
Provide the <code>code</code> value (e.g., <code>49-3023</code>).

- 11-1021 — General and Operations Managers
- 11-2011 — Advertising and Promotions Managers
- 11-3031 — Financial Managers
- 11-9021 — Construction Managers
- 11-9041 — Architectural and Engineering Managers
- 11-9071 — Gaming Managers
- 11-9141 — Property, Real Estate, and Community Association Managers
- 13-1041 — Compliance Officers
- 13-2011 — Accountants and Auditors
- 13-2051 — Financial Analysts
- 13-2052 — Personal Financial Advisors
- 13-2082 — Tax Preparers
- 15-1121 — Computer Systems Analysts
- 15-1131 — Computer Programmers
- 15-1132 — Software Developers, Applications
- 15-1133 — Software Developers, Systems Software
- 15-1141 — Database Administrators
- 15-1142 — Information Security Analysts
- 15-1143 — Computer Network Architects
- 15-1151 — Computer User Support Specialists
- 17-2051 — Civil Engineers
- 17-2071 — Electrical Engineers
- 17-2141 — Mechanical Engineers
- 19-3011 — Economists
- 23-1011 — Lawyers
- 23-2011 — Paralegals and Legal Assistants
- 25-2021 — Elementary School Teachers
- 27-1024 — Graphic Designers
- 27-2012 — Producers and Directors
- 29-1141 — Registered Nurses
- 29-1062 — Family and General Practitioners
- 29-1067 — Surgeons
- 31-9097 — Phlebotomists
- 33-3021 — Detectives and Criminal Investigators
- 35-1011 — Chefs and Head Cooks
- 41-9011 — Demonstrators and Product Promoters
- 41-9021 — Real Estate Brokers
- 43-3071 — Tellers
- 47-1011 — Construction Supervisors
- 47-2061 — Construction Laborers
- 49-3023 — Automotive Service Technicians and Mechanics
- 51-4121 — Welders, Cutters, Solderers, and Brazers
- 53-3032 — Heavy and Tractor-Trailer Truck Drivers
- 53-3041 — Taxi Drivers and Chauffeurs
- SELFEMP — Self-Employed
- UNEMPLO — Unemployed
- RETIRED — Retired
- OTHERXX — Other

## Annual salary (ranges)
- <40k
- 50k–99k
- 100k–149k
- 150k+

## Expected monthly volume
- under $1k
- $1k–$5k
- $5k–$20k
- $20k+

## Account purpose
- everyday spend
- subscriptions
- business expenses
- testing
- other

<Tip>Values above match our app’s KYC form to keep integrations consistent.</Tip>
## Copy as JSON

```json
{
  "fields": [
    { "name": "firstName", "required": true, "type": "string", "maxLength": 50, "description": "Given name." },
    { "name": "lastName", "required": true, "type": "string", "maxLength": 50, "description": "Family name." },
    { "name": "birthDate", "required": true, "type": "date", "description": "YYYY-MM-DD (e.g., 1990-01-01)." },
    {
      "name": "nationalId",
      "required": true,
      "type": "string",
      "regex": "^[0-9A-Za-z-]+$",
      "description": "Government ID number. Letters, numbers, and dashes only."
    },
    {
      "name": "countryOfIssue",
      "required": true,
      "type": "string",
      "minLength": 2,
      "maxLength": 2,
      "description": "ISO-3166-1 alpha-2 country code (e.g., US). Case-insensitive."
    },
    { "name": "email", "required": true, "type": "string", "description": "Email address." },
    {
      "name": "address",
      "required": true,
      "type": "object",
      "description": "Object with line1, line2, city, region, postalCode, countryCode (case-insensitive)."
    },
    {
      "name": "phoneCountryCode",
      "required": false,
      "type": "string",
      "maxLength": 3,
      "description": "Country calling code digits only (e.g., 1)."
    },
    {
      "name": "phoneNumber",
      "required": false,
      "type": "string",
      "maxLength": 15,
      "description": "Phone number digits only; include area code."
    },
    {
      "name": "occupation",
      "required": true,
      "type": "string",
      "description": "SOC occupation code (e.g., 49-3023). Use /kyc/values for the list."
    },
    {
      "name": "annualSalary",
      "required": true,
      "type": "string",
      "enum": ["<40k", "50k–99k", "100k–149k", "150k+"],
      "description": "Choose a salary range."
    },
    {
      "name": "accountPurpose",
      "required": true,
      "type": "string",
      "enum": ["everyday spend", "subscriptions", "business expenses", "testing", "other"],
      "description": "How the account will be used."
    },
    {
      "name": "expectedMonthlyVolume",
      "required": true,
      "type": "string",
      "enum": ["under $1k", "$1k–$5k", "$5k–$20k", "$20k+"],
      "description": "Choose a monthly volume range."
    }
  ],
  "occupations": [
    { "code": "11-1021", "label": "General and Operations Managers" },
    { "code": "11-2011", "label": "Advertising and Promotions Managers" },
    { "code": "11-3031", "label": "Financial Managers" },
    { "code": "11-9021", "label": "Construction Managers" },
    { "code": "11-9041", "label": "Architectural and Engineering Managers" },
    { "code": "11-9071", "label": "Gaming Managers" },
    { "code": "11-9141", "label": "Property, Real Estate, and Community Association Managers" },
    { "code": "13-1041", "label": "Compliance Officers" },
    { "code": "13-2011", "label": "Accountants and Auditors" },
    { "code": "13-2051", "label": "Financial Analysts" },
    { "code": "13-2052", "label": "Personal Financial Advisors" },
    { "code": "13-2082", "label": "Tax Preparers" },
    { "code": "15-1121", "label": "Computer Systems Analysts" },
    { "code": "15-1131", "label": "Computer Programmers" },
    { "code": "15-1132", "label": "Software Developers, Applications" },
    { "code": "15-1133", "label": "Software Developers, Systems Software" },
    { "code": "15-1141", "label": "Database Administrators" },
    { "code": "15-1142", "label": "Information Security Analysts" },
    { "code": "15-1143", "label": "Computer Network Architects" },
    { "code": "15-1151", "label": "Computer User Support Specialists" },
    { "code": "17-2051", "label": "Civil Engineers" },
    { "code": "17-2071", "label": "Electrical Engineers" },
    { "code": "17-2141", "label": "Mechanical Engineers" },
    { "code": "19-3011", "label": "Economists" },
    { "code": "23-1011", "label": "Lawyers" },
    { "code": "23-2011", "label": "Paralegals and Legal Assistants" },
    { "code": "25-2021", "label": "Elementary School Teachers" },
    { "code": "27-1024", "label": "Graphic Designers" },
    { "code": "27-2012", "label": "Producers and Directors" },
    { "code": "29-1141", "label": "Registered Nurses" },
    { "code": "29-1062", "label": "Family and General Practitioners" },
    { "code": "29-1067", "label": "Surgeons" },
    { "code": "31-9097", "label": "Phlebotomists" },
    { "code": "33-3021", "label": "Detectives and Criminal Investigators" },
    { "code": "35-1011", "label": "Chefs and Head Cooks" },
    { "code": "41-9011", "label": "Demonstrators and Product Promoters" },
    { "code": "41-9021", "label": "Real Estate Brokers" },
    { "code": "43-3071", "label": "Tellers" },
    { "code": "47-1011", "label": "Construction Supervisors" },
    { "code": "47-2061", "label": "Construction Laborers" },
    { "code": "49-3023", "label": "Automotive Service Technicians and Mechanics" },
    { "code": "51-4121", "label": "Welders, Cutters, Solderers, and Brazers" },
    { "code": "53-3032", "label": "Heavy and Tractor-Trailer Truck Drivers" },
    { "code": "53-3041", "label": "Taxi Drivers and Chauffeurs" },
    { "code": "SELFEMP", "label": "Self-Employed" },
    { "code": "UNEMPLO", "label": "Unemployed" },
    { "code": "RETIRED", "label": "Retired" },
    { "code": "OTHERXX", "label": "Other" }
  ],
  "annualSalary": ["<40k", "50k–99k", "100k–149k", "150k+"],
  "expectedMonthlyVolume": ["under $1k", "$1k–$5k", "$5k–$20k", "$20k+"],
  "accountPurpose": ["everyday spend", "subscriptions", "business expenses", "testing", "other"]
}
```

## API endpoint (JSON)

```bash
curl -s -H 'Authorization: Bearer <SESSION_TOKEN>' \
  https://dev-api.machines.cash/partner/v1/kyc/values
```
````

### Other MDX pages
#### File: billing.mdx
````mdx
---
title: "Billing"
description: "Per-card pricing by default; additional billable events optional."
---

<Note>Billing endpoints are not currently part of the API guide.</Note>

- Per card created (primary), optional add-ons: one-time card, secrets read, KYC submit.
- Usage endpoints provide cursor pagination and reproducible audit events.
````

#### File: disposable-cards.mdx
````mdx
---
title: "Disposable Cards"
description: "Create disposable card proposals and execute one-time cards."
---

<Note>Disposable card endpoints are available in partner API v1.</Note>

{/*
```mermaid
sequenceDiagram
  participant Agent
  participant MachinesAPI as Machines API

  Agent->>MachinesAPI: POST /partner/v1/cards/disposable/proposals
  MachinesAPI-->>Agent: { disposableId, expiresAt }
  Agent->>MachinesAPI: POST /partner/v1/cards/disposable/execute
  MachinesAPI-->>Agent: { card, disposable }
  Agent->>MachinesAPI: POST /partner/v1/cards/{"{cardId}"}/secrets
  MachinesAPI-->>Agent: encrypted PAN/CVC
  Agent->>MachinesAPI: DELETE /partner/v1/cards/{"{cardId}"}
```

<Tip>Use <code>Idempotency-Key</code> on both propose and execute to make agent retries safe.</Tip>
*/}
````

#### File: webhooks.mdx
````mdx
---
title: "Webhooks"
description: "Subscribe to card + transaction + KYC updates with HMAC signatures."
---

<Note>Webhooks are not currently documented in this guide.</Note>

Events include `kyc.status.updated`, `card.created`, `card.updated`, `transaction.*`, and `three_ds.*`.

<Note>All webhooks are HMAC signed with a shared secret; verify signatures and handle retries idempotently.</Note>
````

## Site configuration (docs.json)
````json
{
  "name": "Machines Cash Docs",
  "theme": "aspen",
  "logo": {
    "dark": "/machines-wordmark.svg",
    "light": "/machines-wordmark.svg"
  },
  "favicon": "/favicon.ico",
  "colors": {
    "primary": "#FF4500",
    "light": "#f7f7f7"
  },
  "api": {
    "playground": {
      "display": "interactive"
    },
    "examples": {
      "languages": [
        "curl",
        "javascript",
        "python"
      ],
      "defaults": "required",
      "prefill": true
    }
  },
  "navigation": {
    "tabs": [
      {
        "tab": "Machines User",
        "groups": [
          {
            "group": "Start",
            "pages": [
              "index",
              "user-kyc",
              "user-cards",
              "user-add-money",
              "user-faq"
            ]
          },
          {
            "group": "Use Your Own AI",
            "pages": [
              "mcp-setup",
              "agent-skills",
              "clawdbot",
              "ai-context-file"
            ]
          },
          {
            "group": "User API",
            "pages": [
              "consumer-api-reference"
            ]
          },
          {
            "group": "User Auth API",
            "openapi": "consumer-openapi.yaml",
            "pages": [
              "POST /user/v1/bootstrap",
              "GET /user/v1/keys",
              "POST /user/v1/keys",
              "PATCH /user/v1/keys/{keyId}",
              "DELETE /user/v1/keys/{keyId}",
              "POST /user/v1/sessions",
              "POST /identity/user-api-keys"
            ]
          },
          {
            "group": "User Crypto API",
            "openapi": "consumer-openapi.yaml",
            "pages": [
              "POST /user/v1/crypto/encrypt",
              "POST /user/v1/crypto/decrypt",
              "GET /user/v1/identity/data-key"
            ]
          },
          {
            "group": "User KYC API",
            "openapi": "consumer-openapi.yaml",
            "pages": [
              "GET /user/v1/users",
              "PATCH /user/v1/users",
              "POST /user/v1/kyc/initiate",
              "POST /user/v1/kyc",
              "GET /user/v1/kyc/status",
              "POST /user/v1/kyc/me/document",
              "GET /user/v1/kyc/{userId}",
              "PATCH /user/v1/kyc/{userId}",
              "POST /user/v1/kyc/{userId}/document",
              "GET /user/v1/agreements",
              "POST /user/v1/agreements",
              "GET /user/v1/onboarding",
              "POST /user/v1/onboarding"
            ]
          },
          {
            "group": "User Cards API",
            "openapi": "consumer-openapi.yaml",
            "pages": [
              "GET /user/v1/cards",
              "GET /user/v1/cards/deleted",
              "POST /user/v1/cards",
              "GET /user/v1/cards/{cardId}",
              "PATCH /user/v1/cards/{cardId}",
              "POST /user/v1/cards/{cardId}/delete-now",
              "POST /user/v1/cards/{cardId}/memo",
              "PATCH /user/v1/cards/{cardId}/memo",
              "DELETE /user/v1/cards/{cardId}/memo",
              "POST /user/v1/cards/reorder",
              "POST /user/v1/cards/secrets/session",
              "POST /user/v1/cards/{cardId}/secrets",
              "GET /user/v1/cards/{cardId}/pin/status",
              "GET /user/v1/cards/{cardId}/pin",
              "POST /user/v1/cards/{cardId}/pin",
              "PUT /user/v1/cards/{cardId}/pin",
              "POST /user/v1/cards/disposable/proposals",
              "POST /user/v1/cards/disposable/execute",
              "GET /user/v1/cards/disposable/{proposalId}",
              "DELETE /user/v1/cards/disposable/{proposalId}"
            ]
          },
          {
            "group": "User Balances & Contracts API",
            "openapi": "consumer-openapi.yaml",
            "pages": [
              "GET /user/v1/balances",
              "GET /user/v1/contracts",
              "POST /user/v1/tokens/metadata",
              "GET /user/v1/transactions"
            ]
          },
          {
            "group": "User Deposits API",
            "openapi": "consumer-openapi.yaml",
            "pages": [
              "GET /user/v1/deposits/assets",
              "POST /user/v1/deposits/preview",
              "POST /user/v1/deposits",
              "GET /user/v1/deposits",
              "GET /user/v1/deposits/{depositId}",
              "POST /user/v1/deposits/{depositId}/poll",
              "POST /user/v1/deposits/{depositId}/refresh",
              "POST /user/v1/deposits/{depositId}/save-address",
              "POST /user/v1/deposits/{depositId}/transfer"
            ]
          },
          {
            "group": "User Identity API",
            "openapi": "consumer-openapi.yaml",
            "pages": [
              "GET /user/v1/identity/wallets",
              "POST /user/v1/identity/wallets",
              "POST /user/v1/identity/wallets/manual",
              "PATCH /user/v1/identity/wallets/{address}",
              "DELETE /user/v1/identity/wallets/{address}",
              "GET /user/v1/identity/aliases",
              "PATCH /user/v1/identity/aliases/{aliasId}",
              "DELETE /user/v1/identity/aliases/{aliasId}",
              "GET /user/v1/identity/usage",
              "GET /user/v1/identity/deposit-preferences",
              "POST /user/v1/identity/deposit-preferences/activate",
              "PUT /user/v1/identity/deposit-preferences",
              "POST /user/v1/identity/deposit-preferences/reorder"
            ]
          },
          {
            "group": "User Payments API",
            "openapi": "consumer-openapi.yaml",
            "pages": [
              "GET /user/v1/payments/subscription-assets",
              "GET /user/v1/payments/subscription-balances",
              "POST /user/v1/payments/token-balances",
              "POST /user/v1/payments/token-metadata",
              "POST /user/v1/payments/native-balances",
              "GET /user/v1/payments/tx-status",
              "GET /user/v1/payments/transaction"
            ]
          },
          {
            "group": "User Additional API",
            "openapi": "consumer-openapi.yaml",
            "pages": [
              "GET /user/v1/folders",
              "POST /user/v1/folders",
              "PATCH /user/v1/folders/{folderId}",
              "DELETE /user/v1/folders/{folderId}",
              "POST /user/v1/folders/reorder",
              "POST /user/v1/contracts",
              "PUT /user/v1/contracts/{contractId}/onramp",
              "POST /user/v1/withdrawals",
              "GET /user/v1/notifications",
              "PUT /user/v1/notifications",
              "POST /user/v1/notifications/push",
              "DELETE /user/v1/notifications/push",
              "POST /user/v1/notifications/test-send",
              "GET /user/v1/subscriptions/plans",
              "GET /user/v1/subscriptions/current",
              "POST /user/v1/subscriptions/promo/validate",
              "POST /user/v1/subscriptions/scheduled/cancel",
              "POST /user/v1/subscriptions/checkout",
              "POST /user/v1/subscriptions/payin",
              "POST /user/v1/subscriptions/addons/extra-card/checkout",
              "POST /user/v1/subscriptions/confirm/payin",
              "POST /user/v1/subscriptions/confirm/balance",
              "POST /user/v1/referrals/validate",
              "GET /user/v1/referrals/me",
              "POST /user/v1/referrals/redeem",
              "GET /user/v1/bills",
              "POST /user/v1/bills",
              "PATCH /user/v1/bills/{billId}",
              "DELETE /user/v1/bills/{billId}",
              "GET /user/v1/bills/calendar",
              "GET /user/v1/bills/suggestions",
              "GET /user/v1/support",
              "POST /user/v1/spotlight/search"
            ]
          }
        ]
      },
      {
        "tab": "Partner",
        "groups": [
          {
            "group": "Start",
            "pages": [
              "partner",
              "getting-started",
              "authentication",
              "encryption",
              "sandbox-testing"
            ]
          },
          {
            "group": "Core Flows",
            "pages": [
              "kyc-flow",
              "agreements",
              "cards",
              "balances",
              "deposits",
              "transactions",
              "withdrawals"
            ]
          },
          {
            "group": "Agentic",
            "pages": [
              "agentic-payments",
              "agent-tool-schemas",
              "best-practices-agents",
              "partners/llms"
            ]
          },
          {
            "group": "Reference",
            "pages": [
              "errors"
            ]
          },
          {
            "group": "Authentication API",
            "openapi": "openapi.yaml",
            "pages": [
              "POST /partner/v1/sessions"
            ]
          },
          {
            "group": "Users API",
            "openapi": "openapi.yaml",
            "pages": [
              "POST /partner/v1/users/resolve"
            ]
          },
          {
            "group": "KYC API",
            "openapi": "openapi.yaml",
            "pages": [
              "GET /partner/v1/kyc/schema",
              "POST /partner/v1/kyc/applications",
              "GET /partner/v1/kyc/status",
              "GET /partner/v1/kyc/values"
            ]
          },
          {
            "group": "Agreements API",
            "openapi": "openapi.yaml",
            "pages": [
              "GET /partner/v1/agreements",
              "POST /partner/v1/agreements"
            ]
          },
          {
            "group": "Cards API",
            "openapi": "openapi.yaml",
            "pages": [
              "GET /partner/v1/cards",
              "POST /partner/v1/cards",
              "GET /partner/v1/cards/{cardId}",
              "PATCH /partner/v1/cards/{cardId}",
              "DELETE /partner/v1/cards/{cardId}",
              "POST /partner/v1/cards/secrets/session",
              "POST /partner/v1/cards/{cardId}/secrets"
            ]
          },
          {
            "group": "Encryption API",
            "openapi": "openapi.yaml",
            "pages": [
              "GET /partner/v1/encryption/data-key",
              "POST /partner/v1/encryption/encrypt",
              "POST /partner/v1/encryption/decrypt"
            ]
          },
          {
            "group": "Balances API",
            "openapi": "openapi.yaml",
            "pages": [
              "GET /partner/v1/balances"
            ]
          },
          {
            "group": "Deposits API",
            "openapi": "openapi.yaml",
            "pages": [
              "GET /partner/v1/deposits/assets",
              "POST /partner/v1/deposits/range",
              "POST /partner/v1/deposits/estimate",
              "POST /partner/v1/deposits",
              "GET /partner/v1/deposits",
              "GET /partner/v1/deposits/{depositId}"
            ]
          },
          {
            "group": "Transactions API",
            "openapi": "openapi.yaml",
            "pages": [
              "GET /partner/v1/transactions"
            ]
          },
          {
            "group": "Withdrawals API",
            "openapi": "openapi.yaml",
            "pages": [
              "GET /partner/v1/withdrawals/assets",
              "POST /partner/v1/withdrawals/range",
              "POST /partner/v1/withdrawals/estimate",
              "POST /partner/v1/withdrawals"
            ]
          },
          {
            "group": "Hidden",
            "hidden": true,
            "pages": [
              "kyc-values"
            ]
          }
        ]
      }
    ]
  }
}
````
## API reference (openapi.yaml)
````yaml
openapi: 3.1.0
jsonSchemaDialect: "https://spec.openapis.org/oas/3.1/dialect/2024-11-10"
info:
  title: Machines Cash API
  version: "0.3.0"
  description: |
    API for KYC, cards, encryption, balances, deposits, transactions, and withdrawals.
    All responses use a consistent envelope by default: { ok, data, summary, errors[], next }.
    Set X-Open-Responses: 1 to receive Open Responses-style response objects.
servers:
  - url: https://api.machines.cash
    description: production
  - url: https://dev-api.machines.cash
    description: sandbox
tags:
  - name: users
  - name: sessions
  - name: kyc
  - name: kycValues
  - name: agreements
  - name: cards
  - name: encryption
  - name: balances
  - name: deposits
  - name: transactions
  - name: withdrawals
paths:
  /partner/v1/users/resolve:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    post:
      operationId: api.users.resolve
      tags: [users]
      summary: Resolve or create a Machines user
      description: Maps your user id to a Machines user (and wallet).
      x-mint:
        href: /api/users/resolve
      security:
        - ApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserResolveRequest"
      responses:
        "200":
          description: User resolved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserResolveResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /partner/v1/sessions:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    post:
      operationId: api.sessions.create
      tags: [sessions]
      summary: Create a short-lived user session token
      description: Use your API key to mint a scoped, short-lived JWT for agent calls.
      x-mint:
        href: /api/sessions
      security:
        - ApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSessionRequest"
      responses:
        "201":
          description: Session created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateSessionResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
  /partner/v1/kyc/schema:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    get:
      operationId: api.kyc.schema
      tags: [kyc]
      summary: Get KYC field requirements
      x-mint:
        href: /api/kyc/schema
      security:
        - ApiKey: []
      responses:
        "200":
          description: KYC schema
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KycSchemaResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /partner/v1/kyc/applications:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    post:
      operationId: api.kyc.submit
      tags: [kyc]
      summary: Submit KYC application data
      x-mint:
        href: /api/kyc/applications
      security:
        - Session: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KycApplicationCreateRequest"
            example:
              firstName: "John"
              lastName: "Smith"
              birthDate: "1990-01-01"
              nationalId: "123456789"
              countryOfIssue: "US"
              email: "jsmith@example.com"
              address:
                line1: "123 Main St"
                line2: "Unit 4"
                city: "New York"
                region: "NY"
                postalCode: "10001"
                countryCode: "US"
              occupation: "49-3023"
              annualSalary: "<40k"
              accountPurpose: "everyday spend"
              expectedMonthlyVolume: "under $1k"
              phoneCountryCode: "1"
              phoneNumber: "4155551234"
      responses:
        "201":
          description: KYC application submitted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KycApplicationResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /partner/v1/kyc/status:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    get:
      operationId: api.kyc.status
      tags: [kyc]
      summary: Get current KYC status
      x-mint:
        href: /api/kyc/status
      security:
        - Session: []
      responses:
        "200":
          description: KYC status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KycApplicationResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /partner/v1/kyc/values:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    get:
      operationId: api.kyc.values
      tags: [kyc]
      summary: Get KYC field requirements and values
      description: Field requirements plus occupation codes and allowed ranges for KYC fields. Use `countryOfIssue` from these fields for country-specific agreement handling.
      x-mint:
        href: /api/kyc/values
      security:
        - Session: []
      responses:
        "200":
          description: KYC values
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KycValuesResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /partner/v1/agreements:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    get:
      operationId: api.agreements.list
      tags: [agreements]
      summary: Get user agreements
      description: Returns the agreement text, links, and current acceptance status.
      x-mint:
        href: /api/agreements/list
      security:
        - Session: []
      responses:
        "200":
          description: Agreements
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgreementsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
    post:
      operationId: api.agreements.accept
      tags: [agreements]
      summary: Accept user agreements
      description: Marks all agreements as accepted for an approved user.
      x-mint:
        href: /api/agreements/accept
      security:
        - Session: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AgreementsAcceptRequest"
      responses:
        "200":
          description: Agreements accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgreementsResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /partner/v1/cards:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    get:
      operationId: api.cards.list
      tags: [cards]
      summary: List cards
      description: Returns cards created by your account only.
      x-mint:
        href: /api/cards/list
      security:
        - Session: []
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: Card list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CardListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    post:
      operationId: api.cards.create
      tags: [cards]
      summary: Create a card
      description: Cards are virtual-only. Card labels are optional and can be set with `POST /encryption/encrypt`. Limits are optional.
      x-mint:
        href: /api/cards/create
      security:
        - Session: []
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CardCreateRequest"
      responses:
        "200":
          description: Idempotent replay
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CardResponse"
        "201":
          description: Card created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CardResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /partner/v1/cards/{cardId}:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    get:
      operationId: api.cards.get
      tags: [cards]
      summary: Get card by id
      x-mint:
        href: /api/cards/get
      security:
        - Session: []
      parameters:
        - $ref: "#/components/parameters/CardId"
      responses:
        "200":
          description: Card
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CardResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      operationId: api.cards.update
      tags: [cards]
      summary: Update card status, limits, or labels
      x-mint:
        href: /api/cards/update
      security:
        - Session: []
      parameters:
        - $ref: "#/components/parameters/CardId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CardUpdateRequest"
      responses:
        "200":
          description: Card updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CardResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
    delete:
      operationId: api.cards.delete
      tags: [cards]
      summary: Delete a card
      description: Cancels the card immediately.
      x-mint:
        href: /api/cards/delete
      security:
        - Session: []
      parameters:
        - $ref: "#/components/parameters/CardId"
      responses:
        "200":
          description: Card deleted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CardDeleteResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /partner/v1/cards/{cardId}/secrets:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    post:
      operationId: api.cards.secrets.get
      tags: [cards]
      summary: Retrieve encrypted card secrets
      description: Requires a secrets sessionId and cards.secrets.read scope.
      x-mint:
        href: /api/cards/secrets
      security:
        - Session: []
      parameters:
        - $ref: "#/components/parameters/CardId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CardSecretsRequest"
      responses:
        "200":
          description: Encrypted card secrets
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CardSecretsResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /partner/v1/cards/secrets/session:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    post:
      operationId: api.cards.secrets.session
      tags: [cards]
      summary: Create a card secrets session
      description: Returns a short-lived sessionId and secretKey for decrypting card PAN/CVC.
      x-mint:
        href: /api/cards/secrets/session
      security:
        - Session: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              description: Optional empty body for clients that require a JSON payload.
            example: {}
      responses:
        "200":
          description: Card secrets session
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CardSecretsSessionResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /partner/v1/encryption/data-key:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    get:
      operationId: api.encryption.data_key
      tags: [encryption]
      summary: Get the per-user data key
      description: Use this key to encrypt/decrypt labels with AES-256-GCM.
      x-mint:
        href: /api/encryption/data-key
      security:
        - Session: []
      responses:
        "200":
          description: Data key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DataKeyResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /partner/v1/encryption/encrypt:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    post:
      operationId: api.encryption.encrypt
      tags: [encryption]
      summary: Encrypt plaintext with the data key
      x-mint:
        href: /api/encryption/encrypt
      security:
        - Session: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EncryptionEncryptRequest"
      responses:
        "200":
          description: Encrypted value
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EncryptionEncryptResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /partner/v1/encryption/decrypt:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    post:
      operationId: api.encryption.decrypt
      tags: [encryption]
      summary: Decrypt ciphertext with the data key
      x-mint:
        href: /api/encryption/decrypt
      security:
        - Session: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EncryptionDecryptRequest"
      responses:
        "200":
          description: Decrypted value
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EncryptionDecryptResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /partner/v1/balances:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    get:
      operationId: api.balances.get
      tags: [balances]
      summary: Get balance details
      description: Returns credit limit, spending power, and charge totals (all in cents).
      x-mint:
        href: /api/balances/get
      security:
        - Session: []
      responses:
        "200":
          description: Balances
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BalancesResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "409":
          $ref: "#/components/responses/Conflict"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /partner/v1/deposits:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    post:
      operationId: api.deposits.create
      tags: [deposits]
      summary: Create a deposit address
      description: Returns a deposit address for this user. Production uses USDC on Base; sandbox uses rUSD on Base Sepolia. No extra scopes required.
      x-mint:
        href: /api/deposits/create
      security:
        - Session: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DepositCreateRequest"
      responses:
        "201":
          description: Deposit created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DepositResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
    get:
      operationId: api.deposits.list
      tags: [deposits]
      summary: List deposits
      description: Returns recent deposits, optionally scoped to active ones. No extra scopes required.
      x-mint:
        href: /api/deposits/list
      security:
        - Session: []
      parameters:
        - $ref: "#/components/parameters/DepositScope"
      responses:
        "200":
          description: Deposit list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DepositListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /partner/v1/deposits/assets:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    get:
      operationId: api.deposits.assets
      tags: [deposits]
      summary: List deposit assets
      description: Returns supported token and network ids for standard partner deposits. Min/max values are not returned by this endpoint.
      x-mint:
        href: /api/deposits/assets
      security:
        - Session: []
      parameters:
        - $ref: "#/components/parameters/DepositAssetsQuery"
        - $ref: "#/components/parameters/DepositAssetsLimit"
      responses:
        "200":
          description: Deposit assets
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DepositAssetsResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /partner/v1/deposits/range:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    post:
      operationId: api.deposits.range
      tags: [deposits]
      summary: Get min/max range for a deposit pair
      description: Returns min/max amount and routing details for a specific `{ currency, network }` pair.
      x-mint:
        href: /api/deposits/range
      security:
        - Session: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DepositRangeRequest"
      responses:
        "200":
          description: Deposit range
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DepositRangeResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /partner/v1/deposits/estimate:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    post:
      operationId: api.deposits.estimate
      tags: [deposits]
      summary: Estimate deposit receive amount
      description: Returns estimated collateral receive amount and routing details for a specific `{ currency, network, amount }` input.
      x-mint:
        href: /api/deposits/estimate
      security:
        - Session: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DepositEstimateRequest"
      responses:
        "200":
          description: Deposit estimate
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DepositEstimateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /partner/v1/deposits/{depositId}:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    get:
      operationId: api.deposits.get
      tags: [deposits]
      summary: Get a deposit
      description: Returns the latest status for a deposit intent. No extra scopes required.
      x-mint:
        href: /api/deposits/get
      security:
        - Session: []
      parameters:
        - $ref: "#/components/parameters/DepositId"
      responses:
        "200":
          description: Deposit
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DepositResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /partner/v1/transactions:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    get:
      operationId: api.transactions.list
      tags: [transactions]
      summary: List transaction history
      description: Returns transaction history for the user. Use `type` to filter by one or more transaction types.
      x-mint:
        href: /api/transactions/list
      security:
        - Session: []
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/CardIdQuery"
        - $ref: "#/components/parameters/TransactionTypeFilter"
      responses:
        "200":
          description: Transactions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "409":
          $ref: "#/components/responses/Conflict"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /partner/v1/withdrawals/assets:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    get:
      operationId: api.withdrawals.assets
      tags: [withdrawals]
      summary: List withdrawal destination assets
      description: Returns destination token/network options. Routes are dynamic and provider-driven; always query this endpoint before quoting. Optional source query fields are accepted as hints.
      x-mint:
        href: /api/withdrawals/assets
      security:
        - Session: []
      parameters:
        - $ref: "#/components/parameters/WithdrawalSourceChainIdQuery"
        - $ref: "#/components/parameters/WithdrawalSourceTokenAddressQuery"
        - $ref: "#/components/parameters/DepositAssetsQuery"
        - $ref: "#/components/parameters/DepositAssetsLimit"
      responses:
        "200":
          description: Withdrawal assets
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WithdrawalAssetsResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /partner/v1/withdrawals/range:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    post:
      operationId: api.withdrawals.range
      tags: [withdrawals]
      summary: Get min/max range for a withdrawal destination
      description: Returns min/max and route details for a destination pair. Routes are dynamic and provider-driven.
      x-mint:
        href: /api/withdrawals/range
      security:
        - Session: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WithdrawalRangeRequest"
      responses:
        "200":
          description: Withdrawal range
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WithdrawalRangeResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /partner/v1/withdrawals/estimate:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    post:
      operationId: api.withdrawals.estimate
      tags: [withdrawals]
      summary: Estimate withdrawal receive amount
      description: Returns estimated destination receive amount for a destination pair and amount. Routes are dynamic and provider-driven.
      x-mint:
        href: /api/withdrawals/estimate
      security:
        - Session: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WithdrawalEstimateRequest"
      responses:
        "200":
          description: Withdrawal estimate
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WithdrawalEstimateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /partner/v1/withdrawals:
    parameters:
      - $ref: "#/components/parameters/OpenResponses"
      - $ref: "#/components/parameters/OpenResponsesCallId"
    post:
      operationId: api.withdrawals.create
      tags: [withdrawals]
      summary: Request a withdrawal signature payload
      description: Creates a ChangeNOW relay route from the selected source to destination and returns Rain withdrawal signature parameters for onchain execution.
      x-mint:
        href: /api/withdrawals/create
      security:
        - Session: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WithdrawalRequest"
      responses:
        "200":
          description: Withdrawal signature payload
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WithdrawalResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
components:
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: X-Partner-Key
    Session:
      type: http
      scheme: bearer
      bearerFormat: JWT
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        minLength: 8
        maxLength: 128
      description: Unique key to prevent duplicate writes.
    OpenResponses:
      name: X-Open-Responses
      in: header
      required: false
      schema:
        type: string
        enum: ["1", "true"]
      description: Return Open Responses-compatible output when set (replaces the default response envelope).
    OpenResponsesCallId:
      name: X-Open-Responses-Call-Id
      in: header
      required: false
      schema:
        type: string
        minLength: 1
      description: Tool call id used for function_call_output items when Open Responses mode is enabled.
    Cursor:
      name: cursor
      in: query
      required: false
      schema:
        type: string
        minLength: 1
      description: Cursor returned by the previous page.
    Limit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 200
      description: Page size (max 200).
    CardIdQuery:
      name: cardId
      in: query
      required: false
      schema:
        type: string
        format: uuid
      description: Filter to a specific card id.
    TransactionTypeFilter:
      name: type
      in: query
      required: false
      schema:
        type: string
      description: Comma-separated transaction types (`spend`, `collateral`, `payment`, `fee`).
    CardId:
      name: cardId
      in: path
      required: true
      schema:
        type: string
        minLength: 8
      description: Machines card id.
    DepositId:
      name: depositId
      in: path
      required: true
      schema:
        type: string
        minLength: 8
      description: Deposit intent id.
    DepositScope:
      name: scope
      in: query
      required: false
      schema:
        type: string
        enum: [active, all]
      description: Return recent active deposits (default) or full history.
    DepositAssetsQuery:
      name: q
      in: query
      required: false
      schema:
        type: string
      description: Optional search query for ticker, name, or network id/label.
    DepositAssetsLimit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
      description: Maximum number of assets to return (max 100).
    WithdrawalSourceChainIdQuery:
      name: sourceChainId
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
      description: Optional source chain hint. Defaults to Base (8453) in production and Base Sepolia in sandbox.
    WithdrawalSourceTokenAddressQuery:
      name: sourceTokenAddress
      in: query
      required: false
      schema:
        $ref: "#/components/schemas/TokenAddress"
      description: Optional source token hint. Defaults to USDC collateral token in production and rUSD token in sandbox.
  responses:
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Forbidden:
      description: Forbidden
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    NotFound:
      description: Not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Conflict:
      description: Conflict
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    ServiceUnavailable:
      description: Service unavailable
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
  schemas:
    ErrorDetail:
      type: object
      additionalProperties: false
      required: [code, message]
      properties:
        code:
          type: string
          example: invalid_request
        message:
          type: string
          example: missing required field
        field:
          type: string
          description: Optional field name that caused the error.
    ErrorResponse:
      type: object
      additionalProperties: false
      required: [ok, summary, errors]
      properties:
        ok:
          type: boolean
          const: false
        summary:
          type: string
          example: invalid request
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    NextHint:
      type: object
      additionalProperties: false
      properties:
        pollAfterMs:
          type: integer
          minimum: 0
        suggestedTool:
          type: string
        suggestedArgs:
          type: object
          additionalProperties: true
    EncryptedField:
      type: object
      description: Encrypted payload generated by `POST /encryption/encrypt` (recommended). Canonical field order is `v`, `iv`, `ct` (order doesn’t matter in JSON). See `/encryption`.
      additionalProperties: false
      required: [v, iv, ct]
      properties:
        v:
          type: integer
          minimum: 1
          maximum: 1000000
          description: Schema version.
          example: 1
        iv:
          type: string
          description: Base64url-encoded IV (12 bytes).
          example: "Qz4f2m7Hk1P9x0ab"
        ct:
          type: string
          description: Base64url-encoded ciphertext + auth tag.
          example: "Hdbb2yT2F4xWZL5m5bJX3eJb4n8wVhjPzqLw2h7i"
    EncryptedFieldNullable:
      anyOf:
        - $ref: "#/components/schemas/EncryptedField"
        - type: "null"
    KycValuesResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [fields, occupations, annualSalary, expectedMonthlyVolume, accountPurpose]
          properties:
            fields:
              type: array
              items:
                $ref: "#/components/schemas/KycSchemaField"
            occupations:
              type: array
              items:
                type: object
                required: [code, label]
                properties:
                  code:
                    type: string
                  label:
                    type: string
            annualSalary:
              type: array
              items:
                type: string
            expectedMonthlyVolume:
              type: array
              items:
                type: string
            accountPurpose:
              type: array
              items:
                type: string
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    AgreementLink:
      type: object
      additionalProperties: false
      required: [label, url]
      properties:
        label:
          type: string
        url:
          type: string
    Agreement:
      type: object
      additionalProperties: false
      required: [id, text, links]
      properties:
        id:
          type: string
        text:
          type: string
        links:
          type: array
          items:
            $ref: "#/components/schemas/AgreementLink"
    AgreementsAcceptRequest:
      type: object
      additionalProperties: false
      required: [accepted]
      properties:
        accepted:
          type: boolean
          const: true
          description: Must be true to accept all agreements.
    AgreementsResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [agreements, accepted, acceptedAt]
          properties:
            agreements:
              type: array
              items:
                $ref: "#/components/schemas/Agreement"
            accepted:
              type: boolean
            acceptedAt:
              type: [string, "null"]
              format: date-time
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    Balances:
      type: object
      additionalProperties: false
      required: [creditLimit, pendingCharges, postedCharges, balanceDue, spendingPower]
      properties:
        creditLimit:
          type: [number, "null"]
          description: Credit limit in cents.
        pendingCharges:
          type: [number, "null"]
          description: Pending charges in cents.
        postedCharges:
          type: [number, "null"]
          description: Posted charges in cents.
        balanceDue:
          type: [number, "null"]
          description: Balance due in cents.
        spendingPower:
          type: [number, "null"]
          description: Available spending power in cents.
    BalancesResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [balances]
          properties:
            balances:
              $ref: "#/components/schemas/Balances"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    TransactionType:
      type: string
      enum: [spend, collateral, payment, fee]
    Transaction:
      type: object
      additionalProperties: false
      required: [transactionId, type, status, amountCents, currency, createdAt]
      properties:
        transactionId:
          type: string
        type:
          $ref: "#/components/schemas/TransactionType"
        status:
          type: string
        amountCents:
          type: integer
        currency:
          type: string
        merchantName:
          type: string
        cardId:
          type: string
        createdAt:
          type: string
          format: date-time
    TransactionListResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [transactions]
          properties:
            transactions:
              type: array
              items:
                $ref: "#/components/schemas/Transaction"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    WalletAddress:
      type: string
      pattern: "^0x[a-fA-F0-9]{40}$"
      example: "0x2b0f7f2f7c8e4c3d2d3b3f6a8f9b0c1d2e3f4a5b"
    TokenAddress:
      type: string
      pattern: "^0x[a-fA-F0-9]{40}$"
      example: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
    UserResolveRequest:
      type: object
      additionalProperties: false
      required: [userId]
      properties:
        userId:
          type: string
          minLength: 1
          maxLength: 120
          description: Your unique id for this user.
          example: user-123
        walletAddress:
          $ref: "#/components/schemas/WalletAddress"
        walletLabel:
          type: string
          maxLength: 120
          description: Optional label for display.
          example: Main Wallet
    User:
      type: object
      additionalProperties: false
      required: [userId, walletAddress, kycStatus, createdAt]
      properties:
        userId:
          type: string
          description: Your user id.
        walletAddress:
          type: [string, "null"]
        kycStatus:
          $ref: "#/components/schemas/KycStatus"
        createdAt:
          type: string
          format: date-time
    UserResolveResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/User"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    CreateSessionRequest:
      type: object
      additionalProperties: false
      required: [userId, scopes]
      properties:
        userId:
          type: string
          minLength: 1
          maxLength: 120
          description: Your user id (must match users.resolve).
          example: user-123
        walletAddress:
          $ref: "#/components/schemas/WalletAddress"
        scopes:
          type: array
          minItems: 1
          description: Scopes to grant to the session token. Deposits and balances do not require extra scopes.
          example: [kyc.read, kyc.write]
          items:
            type: string
            enum:
              - users.read
              - users.write
              - kyc.read
              - kyc.write
              - cards.read
              - cards.write
              - cards.secrets.read
              - encryption.read
              - encryption.write
              - deposits.read
              - deposits.write
              - transactions.read
              - withdrawals.write
        ttlSeconds:
          type: integer
          minimum: 60
          maximum: 86400
          description: Time-to-live in seconds (default 900).
          example: 900
        sessionLabel:
          type: string
          maxLength: 120
          description: Optional label for auditing.
    SessionToken:
      type: object
      additionalProperties: false
      required: [sessionToken, sessionId, userId, expiresAt, scopes]
      properties:
        sessionToken:
          type: string
          description: Bearer token for agent calls.
        sessionId:
          type: string
        userId:
          type: string
          description: Your user id.
        expiresAt:
          type: string
          format: date-time
        scopes:
          type: array
          items:
            type: string
    CreateSessionResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/SessionToken"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    KycStatus:
      type: string
      enum: [not_submitted, pending, approved, denied, locked, canceled, needs_information, needs_verification, manual_review]
    KycSchemaField:
      type: object
      additionalProperties: false
      required: [name, required, type]
      properties:
        name:
          type: string
        required:
          type: boolean
        type:
          type: string
        maxLength:
          type: integer
        minLength:
          type: integer
        regex:
          type: string
        enum:
          type: array
          items:
            type: string
        description:
          type: string
    KycSchemaResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [fields]
          properties:
            fields:
              type: array
              items:
                $ref: "#/components/schemas/KycSchemaField"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    KycAddress:
      type: object
      additionalProperties: false
      required: [line1, city, region, postalCode, countryCode]
      properties:
        line1:
          type: string
          example: "123 Main St"
        line2:
          type: string
          example: "Unit 4"
        city:
          type: string
          example: "New York"
        region:
          type: string
          example: "NY"
        postalCode:
          type: string
          example: "10001"
        countryCode:
          type: string
          minLength: 2
          maxLength: 2
          description: ISO-3166-1 alpha-2 country code (e.g., US). Case-insensitive.
          example: "US"
    KycApplicationCreateRequest:
      type: object
      additionalProperties: false
      required:
        - firstName
        - lastName
        - birthDate
        - nationalId
        - countryOfIssue
        - email
        - address
        - occupation
        - annualSalary
        - accountPurpose
        - expectedMonthlyVolume
      properties:
        firstName:
          type: string
          maxLength: 50
        lastName:
          type: string
          maxLength: 50
        birthDate:
          type: string
          description: YYYY-MM-DD
          example: 1990-01-01
        nationalId:
          type: string
        countryOfIssue:
          type: string
          minLength: 2
          maxLength: 2
          description: ISO-3166-1 alpha-2 country code (e.g., US). Case-insensitive.
          example: "US"
        email:
          type: string
          format: email
          example: "jsmith@example.com"
        address:
          $ref: "#/components/schemas/KycAddress"
        phoneCountryCode:
          type: string
          maxLength: 3
          description: Country calling code digits only (e.g., 1).
          example: "1"
        phoneNumber:
          type: string
          maxLength: 15
          description: Phone number digits only (include area code).
          example: "4155551234"
        occupation:
          type: string
          description: SOC occupation code (e.g., 49-3023). See GET /kyc/values.
          example: "49-3023"
        annualSalary:
          type: string
          enum: ["<40k", "50k–99k", "100k–149k", "150k+"]
          example: "<40k"
        accountPurpose:
          type: string
          enum: ["everyday spend", "subscriptions", "business expenses", "testing", "other"]
          example: "everyday spend"
        expectedMonthlyVolume:
          type: string
          enum: ["under $1k", "$1k–$5k", "$5k–$20k", "$20k+"]
          example: "under $1k"
    KycApplication:
      type: object
      additionalProperties: false
      required: [status, isTermsOfServiceAccepted]
      properties:
        status:
          $ref: "#/components/schemas/KycStatus"
        reason:
          type: [string, "null"]
        completionLink:
          type: [string, "null"]
        externalVerificationLink:
          type: [string, "null"]
        isActive:
          type: [boolean, "null"]
        isTermsOfServiceAccepted:
          type: boolean
    KycApplicationResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/KycApplication"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    CardStatus:
      type: string
      enum: [active, locked, canceled, not_activated]
    CardLimitFrequency:
      type: string
      enum: [perAuthorization, per24HourPeriod, per7DayPeriod, per30DayPeriod, perYearPeriod, allTime]
    CardLimit:
      type: object
      additionalProperties: false
      required: [amountCents, frequency]
      properties:
        amountCents:
          type: integer
          minimum: 1
          description: Limit amount in cents.
          example: 5000
        frequency:
          $ref: "#/components/schemas/CardLimitFrequency"
    Card:
      type: object
      additionalProperties: false
      required:
        - cardId
        - status
        - last4
        - expirationMonth
        - expirationYear
        - encryptedName
        - isPinned
        - sortOrder
        - createdAt
      properties:
        cardId:
          type: string
        status:
          $ref: "#/components/schemas/CardStatus"
        brand:
          type: string
          example: VISA
        last4:
          type: string
        expirationMonth:
          type: integer
        expirationYear:
          type: integer
        limit:
          anyOf:
            - $ref: "#/components/schemas/CardLimit"
            - type: "null"
        encryptedName:
          $ref: "#/components/schemas/EncryptedFieldNullable"
        encryptedColor:
          $ref: "#/components/schemas/EncryptedFieldNullable"
        encryptedEmoji:
          $ref: "#/components/schemas/EncryptedFieldNullable"
        encryptedMemo:
          $ref: "#/components/schemas/EncryptedFieldNullable"
        folderId:
          type: [string, "null"]
        contractId:
          type: [string, "null"]
        isPinned:
          type: boolean
        sortOrder:
          type: integer
        createdAt:
          type: string
          format: date-time
    CardCreateRequest:
      type: object
      additionalProperties: false
      properties:
        encryptedName:
          allOf:
            - $ref: "#/components/schemas/EncryptedField"
          description: Optional encrypted card label. Generate with `POST /encryption/encrypt`.
        encryptedColor:
          allOf:
            - $ref: "#/components/schemas/EncryptedField"
          description: Optional encrypted card color. Generate with `POST /encryption/encrypt`.
        encryptedEmoji:
          allOf:
            - $ref: "#/components/schemas/EncryptedField"
          description: Optional encrypted card emoji. Generate with `POST /encryption/encrypt`.
        encryptedMemo:
          allOf:
            - $ref: "#/components/schemas/EncryptedField"
          description: Optional encrypted card memo. Generate with `POST /encryption/encrypt`.
        limit:
          $ref: "#/components/schemas/CardLimit"
        folderId:
          type: string
        isPinned:
          type: boolean
        sortOrder:
          type: integer
      example:
        limit:
          amountCents: 2500
          frequency: perAuthorization
    CardUpdateRequest:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        status:
          $ref: "#/components/schemas/CardStatus"
        limit:
          $ref: "#/components/schemas/CardLimit"
        encryptedName:
          allOf:
            - $ref: "#/components/schemas/EncryptedField"
          description: Optional encrypted card label. Generate with `POST /encryption/encrypt`.
        encryptedColor:
          allOf:
            - $ref: "#/components/schemas/EncryptedField"
          description: Optional encrypted card color. Generate with `POST /encryption/encrypt`.
        encryptedEmoji:
          allOf:
            - $ref: "#/components/schemas/EncryptedField"
          description: Optional encrypted card emoji. Generate with `POST /encryption/encrypt`.
        encryptedMemo:
          allOf:
            - $ref: "#/components/schemas/EncryptedField"
          description: Optional encrypted card memo. Generate with `POST /encryption/encrypt`.
        folderId:
          type: [string, "null"]
        isPinned:
          type: boolean
        sortOrder:
          type: integer
      example:
        status: locked
    CardResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/Card"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    CardListResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [cards]
          properties:
            cards:
              type: array
              items:
                $ref: "#/components/schemas/Card"
            nextCursor:
              type: string
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    CardDeleteResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [cardId, status, deletedAt]
          properties:
            cardId:
              type: string
            status:
              $ref: "#/components/schemas/CardStatus"
            deletedAt:
              type: string
              format: date-time
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    CardSecretsRequest:
      type: object
      additionalProperties: false
      required: [sessionId]
      properties:
        sessionId:
          type: string
          description: SessionId returned from the secrets session helper.
    CardSecretsSession:
      type: object
      additionalProperties: false
      required: [sessionId, secretKey]
      properties:
        sessionId:
          type: string
          description: SessionId used to fetch encrypted PAN/CVC.
        secretKey:
          type: string
          description: 16-byte secret (hex) used to decrypt PAN/CVC (AES-128-GCM).
    CardSecretsSessionResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/CardSecretsSession"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    CardSecretBlob:
      type: object
      additionalProperties: false
      required: [data, iv]
      properties:
        data:
          type: string
          description: Base64-encoded ciphertext + auth tag.
        iv:
          type: string
          description: Base64-encoded IV.
    CardSecrets:
      type: object
      additionalProperties: false
      required: [encryptedPan, encryptedCvc, expirationMonth, expirationYear, last4]
      properties:
        encryptedPan:
          $ref: "#/components/schemas/CardSecretBlob"
        encryptedCvc:
          $ref: "#/components/schemas/CardSecretBlob"
        expirationMonth:
          type: integer
        expirationYear:
          type: integer
        last4:
          type: string
        brand:
          type: string
        keyId:
          type: string
    CardSecretsResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/CardSecrets"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    DataKeyResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [dataKey]
          properties:
            dataKey:
              type: string
              description: Base64url-encoded 32-byte key.
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    EncryptionEncryptRequest:
      type: object
      additionalProperties: false
      required: [value]
      properties:
        value:
          type: string
          description: Plaintext value to encrypt.
      example:
        value: "Ops Card"
    EncryptionEncryptResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [value]
          properties:
            value:
              $ref: "#/components/schemas/EncryptedField"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    EncryptionDecryptRequest:
      type: object
      additionalProperties: false
      required: [value]
      properties:
        value:
          allOf:
            - $ref: "#/components/schemas/EncryptedField"
          description: Encrypted payload from `POST /encryption/encrypt`.
      example:
        value:
          v: 1
          iv: "Qz4f2m7Hk1P9x0ab"
          ct: "Hdbb2yT2F4xWZL5m5bJX3eJb4n8wVhjPzqLw2h7i"
    EncryptionDecryptResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [value]
          properties:
            value:
              type: string
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    DepositStatus:
      type: string
      enum: [pending, awaitingDeposit, processing, completed, failed, canceled]
    DepositCreateRequest:
      type: object
      additionalProperties: false
      required: [currency, network]
      properties:
        currency:
          type: string
          description: Asset ticker. Production uses usdc; sandbox uses rusd.
          example: usdc
        network:
          type: string
          description: Network slug. Production uses base; sandbox uses base (Base Sepolia).
          example: base
        amount:
          type: number
          exclusiveMinimum: 0
          description: Optional amount to deposit. optional.
    DepositAssetNetwork:
      type: object
      additionalProperties: false
      required: [id, label]
      properties:
        id:
          type: string
          description: Network id to pass as `network` in deposit requests.
        label:
          type: string
          description: Human-readable network label.
        chainId:
          type: [integer, "null"]
        tokenContract:
          type: [string, "null"]
    DepositAsset:
      type: object
      additionalProperties: false
      required: [ticker, name, icon, networks]
      properties:
        ticker:
          type: string
          description: Token ticker to pass as `currency` in deposit requests.
        name:
          type: string
        icon:
          type: [string, "null"]
        networks:
          type: array
          items:
            $ref: "#/components/schemas/DepositAssetNetwork"
    DepositAssetsResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [assets]
          properties:
            assets:
              type: array
              items:
                $ref: "#/components/schemas/DepositAsset"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    DepositRangeRequest:
      type: object
      additionalProperties: false
      required: [currency, network]
      properties:
        currency:
          type: string
          description: Token ticker from `GET /partner/v1/deposits/assets`.
          example: usdc
        network:
          type: string
          description: Network id from `GET /partner/v1/deposits/assets`.
          example: base
    DepositRange:
      type: object
      additionalProperties: false
      required:
        - fromCurrency
        - fromNetwork
        - toCurrency
        - toNetwork
        - contractId
        - payoutAddress
        - payoutChainId
        - minAmount
        - maxAmount
      properties:
        fromCurrency:
          type: string
        fromNetwork:
          type: string
        toCurrency:
          type: string
        toNetwork:
          type: string
        contractId:
          type: string
        payoutAddress:
          type: string
        payoutChainId:
          type: [integer, "null"]
        minAmount:
          type: [number, "null"]
        maxAmount:
          type: [number, "null"]
    DepositRangeResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [range]
          properties:
            range:
              $ref: "#/components/schemas/DepositRange"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    DepositEstimateRequest:
      type: object
      additionalProperties: false
      required: [currency, network, amount]
      properties:
        currency:
          type: string
          description: Token ticker from `GET /partner/v1/deposits/assets`.
          example: hbar
        network:
          type: string
          description: Network id from `GET /partner/v1/deposits/assets`.
          example: hbar
        amount:
          type: number
          exclusiveMinimum: 0
        amountCurrency:
          type: string
          enum: [crypto, usd]
          description: Quote input amount mode. Defaults to `crypto`.
    DepositEstimate:
      type: object
      additionalProperties: false
      required:
        - fromCurrency
        - fromNetwork
        - toCurrency
        - toNetwork
        - contractId
        - payoutAddress
        - payoutChainId
        - quotedAmount
        - quotedAmountCurrency
        - estimatedToAmount
        - minAmount
        - maxAmount
        - rateId
      properties:
        fromCurrency:
          type: string
        fromNetwork:
          type: string
        toCurrency:
          type: string
        toNetwork:
          type: string
        contractId:
          type: string
        payoutAddress:
          type: string
        payoutChainId:
          type: [integer, "null"]
        quotedAmount:
          type: number
        quotedAmountCurrency:
          type: string
          enum: [crypto, usd]
        estimatedToAmount:
          type: [number, "null"]
        minAmount:
          type: [number, "null"]
        maxAmount:
          type: [number, "null"]
        rateId:
          type: [string, "null"]
    DepositEstimateResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [estimate]
          properties:
            estimate:
              $ref: "#/components/schemas/DepositEstimate"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    Deposit:
      type: object
      additionalProperties: false
      required:
        - id
        - contractId
        - changeNowId
        - fromCurrency
        - fromNetwork
        - depositAddress
        - payinExtraId
        - chainId
        - minAmount
        - maxAmount
        - status
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        contractId:
          type: string
        changeNowId:
          type: [string, "null"]
          description: Provider order id (nullable).
        fromCurrency:
          type: string
        fromNetwork:
          type: string
        minAmount:
          type: [number, "null"]
        maxAmount:
          type: [number, "null"]
        depositAddress:
          type: [string, "null"]
          description: Address to send funds (USDC on Base in production).
        payinExtraId:
          type: [string, "null"]
          description: Optional memo/tag required by some deposit networks.
        chainId:
          type: [number, "null"]
          description: Chain id for the deposit network.
        status:
          $ref: "#/components/schemas/DepositStatus"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    DepositResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [deposit]
          properties:
            deposit:
              $ref: "#/components/schemas/Deposit"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    DepositListResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [deposits]
          properties:
            deposits:
              type: array
              items:
                $ref: "#/components/schemas/Deposit"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    WithdrawalRequest:
      type: object
      additionalProperties: false
      required: [amountCents, destination]
      properties:
        amountCents:
          type: integer
          minimum: 1
          description: Amount in cents.
        source:
          $ref: "#/components/schemas/WithdrawalSource"
        destination:
          $ref: "#/components/schemas/WithdrawalDestination"
        adminAddress:
          $ref: "#/components/schemas/WalletAddress"
    WithdrawalSource:
      type: object
      additionalProperties: false
      properties:
        chainId:
          type: integer
          minimum: 1
          description: Optional source chain hint. Defaults to Base (8453) in production and Base Sepolia in sandbox.
        tokenAddress:
          $ref: "#/components/schemas/TokenAddress"
          description: Optional source token hint. Defaults to USDC collateral token in production and rUSD token in sandbox.
        contractId:
          type: [string, "null"]
          format: uuid
    WithdrawalDestination:
      type: object
      additionalProperties: false
      required: [currency, network, address]
      properties:
        currency:
          type: string
          example: hbar
        network:
          type: string
          example: hbar
        address:
          type: string
          minLength: 1
          maxLength: 200
          description: Destination payout address for ChangeNOW.
        extraId:
          type: [string, "null"]
          description: Optional destination memo/tag for payout networks that require it.
    WithdrawalDestinationQuote:
      type: object
      additionalProperties: false
      required: [currency, network]
      properties:
        currency:
          type: string
          example: hbar
        network:
          type: string
          example: hbar
        extraId:
          type: [string, "null"]
          description: Optional destination memo/tag for payout networks that require it.
    WithdrawalAssetNetwork:
      type: object
      additionalProperties: false
      required: [id, label, supportsExtraId]
      properties:
        id:
          type: string
        label:
          type: string
        chainId:
          type: [integer, "null"]
        tokenContract:
          type: [string, "null"]
        supportsExtraId:
          type: boolean
    WithdrawalAsset:
      type: object
      additionalProperties: false
      required: [ticker, name, icon, networks]
      properties:
        ticker:
          type: string
        name:
          type: string
        icon:
          type: [string, "null"]
        networks:
          type: array
          items:
            $ref: "#/components/schemas/WithdrawalAssetNetwork"
    WithdrawalAssetsResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [assets]
          properties:
            assets:
              type: array
              items:
                $ref: "#/components/schemas/WithdrawalAsset"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    WithdrawalRangeRequest:
      type: object
      additionalProperties: false
      required: [destination]
      properties:
        source:
          $ref: "#/components/schemas/WithdrawalSource"
        destination:
          $ref: "#/components/schemas/WithdrawalDestinationQuote"
    WithdrawalRange:
      type: object
      additionalProperties: false
      required:
        - fromCurrency
        - fromNetwork
        - toCurrency
        - toNetwork
        - minAmount
        - maxAmount
        - minAmountCents
        - maxAmountCents
        - destinationSupportsExtraId
      properties:
        fromCurrency:
          type: string
        fromNetwork:
          type: string
        toCurrency:
          type: string
        toNetwork:
          type: string
        minAmount:
          type: [number, "null"]
        maxAmount:
          type: [number, "null"]
        minAmountCents:
          type: [integer, "null"]
        maxAmountCents:
          type: [integer, "null"]
        destinationSupportsExtraId:
          type: boolean
    WithdrawalRangeResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [range]
          properties:
            range:
              $ref: "#/components/schemas/WithdrawalRange"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    WithdrawalEstimateRequest:
      type: object
      additionalProperties: false
      required: [destination, amountCents]
      properties:
        source:
          $ref: "#/components/schemas/WithdrawalSource"
        destination:
          $ref: "#/components/schemas/WithdrawalDestinationQuote"
        amountCents:
          type: integer
          minimum: 1
    WithdrawalEstimate:
      type: object
      additionalProperties: false
      required:
        - fromCurrency
        - fromNetwork
        - toCurrency
        - toNetwork
        - minAmount
        - maxAmount
        - minAmountCents
        - maxAmountCents
        - destinationSupportsExtraId
        - fromAmount
        - fromAmountCents
        - estimatedToAmount
        - rateId
        - destinationExtraId
      properties:
        fromCurrency:
          type: string
        fromNetwork:
          type: string
        toCurrency:
          type: string
        toNetwork:
          type: string
        minAmount:
          type: [number, "null"]
        maxAmount:
          type: [number, "null"]
        minAmountCents:
          type: [integer, "null"]
        maxAmountCents:
          type: [integer, "null"]
        destinationSupportsExtraId:
          type: boolean
        fromAmount:
          type: number
        fromAmountCents:
          type: integer
        estimatedToAmount:
          type: [number, "null"]
        rateId:
          type: [string, "null"]
        destinationExtraId:
          type: [string, "null"]
    WithdrawalEstimateResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          additionalProperties: false
          required: [estimate]
          properties:
            estimate:
              $ref: "#/components/schemas/WithdrawalEstimate"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
    WithdrawalRelay:
      type: object
      additionalProperties: false
      required:
        - changeNowId
        - payinAddress
        - payinExtraId
        - payoutAddress
        - payoutExtraId
        - fromCurrency
        - fromNetwork
        - toCurrency
        - toNetwork
      properties:
        changeNowId:
          type: string
        payinAddress:
          type: string
        payinExtraId:
          type: [string, "null"]
        payoutAddress:
          type: string
        payoutExtraId:
          type: [string, "null"]
        fromCurrency:
          type: string
        fromNetwork:
          type: string
        toCurrency:
          type: string
        toNetwork:
          type: string
    WithdrawalPayload:
      type: object
      additionalProperties: false
      required: [status]
      properties:
        status:
          type: string
          enum: [pending, ready]
        retryAfterSeconds:
          type: [integer, "null"]
        parameters:
          type: [array, "null"]
          items:
            type: string
        execution:
          anyOf:
            - $ref: "#/components/schemas/WithdrawalExecution"
            - type: "null"
        relay:
          anyOf:
            - $ref: "#/components/schemas/WithdrawalRelay"
            - type: "null"
        signature:
          anyOf:
            - $ref: "#/components/schemas/WithdrawalSignature"
            - type: "null"
        expiresAt:
          type: [string, "null"]
          format: date-time
    WithdrawalExecution:
      type: object
      additionalProperties: false
      required:
        - contractId
        - contractVersion
        - chainId
        - collateralProxyAddress
        - controllerAddress
        - coordinatorAddress
        - callTarget
        - callPath
      properties:
        contractId:
          type: [string, "null"]
        contractVersion:
          type: [integer, "null"]
        chainId:
          type: [integer, "null"]
        collateralProxyAddress:
          type: [string, "null"]
        controllerAddress:
          type: [string, "null"]
        coordinatorAddress:
          type: [string, "null"]
        callTarget:
          type: [string, "null"]
        callPath:
          type: string
          enum: [controller_v1, coordinator_v2, unknown]
    WithdrawalSignature:
      type: object
      additionalProperties: false
      properties:
        data:
          type: [string, "null"]
        salt:
          type: [string, "null"]
        parameters:
          type: array
          items:
            type: string
    WithdrawalResponse:
      type: object
      additionalProperties: false
      required: [ok, data, summary, errors]
      properties:
        ok:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/WithdrawalPayload"
        summary:
          type: string
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ErrorDetail"
        next:
          $ref: "#/components/schemas/NextHint"
````
## Supporting script (password-gate.js)
````js
// Client-side password gate (deterrent only; not real access control).
(function () {
  var PASSWORD = "machines"; // case-insensitive check
  var STORAGE_KEY = "machines_docs_pw_ok_v1";
  var storage = null;
  var hasAccessInMemory = false;
  var prevHtmlOverflow = "";
  var prevBodyOverflow = "";

  try {
    storage = window.localStorage;
  } catch (e) {
    storage = null;
  }
  if (!storage) {
    try {
      storage = window.sessionStorage;
    } catch (e2) {
      storage = null;
    }
  }

  function hasAccess() {
    if (storage) {
      try {
        return storage.getItem(STORAGE_KEY) === "1";
      } catch (e) {
        return hasAccessInMemory;
      }
    }
    return hasAccessInMemory;
  }

  function setAccess() {
    hasAccessInMemory = true;
    if (storage) {
      try {
        storage.setItem(STORAGE_KEY, "1");
      } catch (e) {
        // Ignore storage errors; in-memory flag still allows current session.
      }
    }
  }

  function lockScroll(lock) {
    if (lock) {
      prevHtmlOverflow = document.documentElement.style.overflow || "";
      prevBodyOverflow = document.body.style.overflow || "";
      document.documentElement.style.overflow = "hidden";
      document.body.style.overflow = "hidden";
    } else {
      document.documentElement.style.overflow = prevHtmlOverflow;
      document.body.style.overflow = prevBodyOverflow;
    }
  }

  function createStyles() {
    var style = document.createElement("style");
    style.setAttribute("data-password-gate", "true");
    style.textContent =
      "#password-gate{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;background:rgba(8,10,12,.78);backdrop-filter:saturate(120%) blur(6px);}"
      + "#password-gate .pg-card{width:min(420px,92vw);background:#ffffff;color:#111827;border-radius:14px;padding:24px 22px 20px;box-shadow:0 12px 40px rgba(0,0,0,.35);font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;}"
      + "#password-gate .pg-title{font-size:20px;font-weight:700;margin:0 0 6px;}"
      + "#password-gate .pg-sub{font-size:13px;line-height:1.45;margin:0 0 14px;color:#4b5563;}"
      + "#password-gate .pg-form{display:flex;gap:10px;align-items:center;}"
      + "#password-gate .pg-input{flex:1;border:1px solid #e5e7eb;border-radius:10px;padding:10px 12px;font-size:14px;outline:none;}"
      + "#password-gate .pg-input:focus{border-color:#111827;box-shadow:0 0 0 3px rgba(17,24,39,.12);}"
      + "#password-gate .pg-button{border:0;border-radius:10px;padding:10px 14px;font-size:14px;font-weight:600;background:#111827;color:#fff;cursor:pointer;}"
      + "#password-gate .pg-button:hover{background:#0b1220;}"
      + "#password-gate .pg-error{margin-top:10px;font-size:13px;color:#b91c1c;min-height:16px;}";
    return style;
  }

  function showGate() {
    if (document.getElementById("password-gate")) return;

    var overlay = document.createElement("div");
    overlay.id = "password-gate";

    var card = document.createElement("div");
    card.className = "pg-card";

    var title = document.createElement("div");
    title.className = "pg-title";
    title.textContent = "Password required";

    var sub = document.createElement("div");
    sub.className = "pg-sub";
    sub.textContent = "contact help@machines.cash";

    var form = document.createElement("form");
    form.className = "pg-form";
    form.setAttribute("autocomplete", "off");

    var input = document.createElement("input");
    input.className = "pg-input";
    input.type = "password";
    input.name = "password";
    input.placeholder = "Enter password";
    input.setAttribute("aria-label", "Password");

    var button = document.createElement("button");
    button.className = "pg-button";
    button.type = "submit";
    button.textContent = "Unlock";

    var error = document.createElement("div");
    error.className = "pg-error";
    error.setAttribute("aria-live", "polite");

    form.appendChild(input);
    form.appendChild(button);

    form.addEventListener("submit", function (event) {
      event.preventDefault();
      var attempt = (input.value || "").trim().toLowerCase();
      if (!attempt) {
        error.textContent = "Please enter a password.";
        return;
      }
      if (attempt === PASSWORD) {
        setAccess();
        lockScroll(false);
        overlay.remove();
        input.value = "";
        return;
      }
      error.textContent = "Incorrect password.";
      input.focus();
      input.select();
    });

    input.addEventListener("input", function () {
      if (error.textContent) error.textContent = "";
    });

    card.appendChild(title);
    card.appendChild(sub);
    card.appendChild(form);
    card.appendChild(error);

    overlay.appendChild(card);
    document.head.appendChild(createStyles());
    document.body.appendChild(overlay);
    lockScroll(true);

    setTimeout(function () {
      input.focus();
    }, 0);
  }

  function init() {
    if (hasAccess()) return;
    if (!document.body) {
      setTimeout(init, 50);
      return;
    }
    showGate();
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})();
````

## Assets (binary files; not inlined)
- favicon.ico
- logo-dark.svg
- logo-light.svg
- machines-wordmark.svg