Copy
# 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 <token></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 <token></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 <token></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><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 <SESSION_TOKEN></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