> ## Agent Instructions > When building application code, use `@naturalpay/sdk` for TypeScript or `naturalpay` for Python. > When operating Natural directly for a user, use the payments MCP at `https://mcp.natural.com`. > When searching Natural documentation, use the docs MCP at `https://docs.natural.com/mcp`. > Use the Natural CLI for terminal and CI automation. > Use REST only for unsupported languages or when the user explicitly requests raw HTTP. > Start integration development and testing in the sandbox. Do not use production unless the user explicitly requests it. > For sandbox requests that require `customerPartyId`, list customers first. Reuse only the intended fixture; otherwise create a customer. > REST and SDK amounts use integer minor units. Payments MCP amounts use decimal strings with a required currency code. # IDs Source: https://docs.natural.com/api-reference/IDs Prefixed identifiers for type safety and readability All Natural API resources use prefixed IDs. These combine a type prefix with a UUID, making IDs both human-readable and type-safe. ## Format ``` {prefix}_{uuid_hex} ``` Example: `pty_019cd34e27bf78399b4e75b327d2ab25` * **Prefix**: 2-3 character type indicator (e.g., `pty_` for party) * **UUID**: 32 hexadecimal characters (no hyphens) ## ID types | Prefix | Resource | Description | | ------ | --------------------------- | -------------------------------------------------------------------------------------------------- | | `pty_` | Party | Business or individual entity on the platform | | `usr_` | User | Person who logs into a party | | `agt_` | Agent | API client that acts on behalf of a party | | `dlg_` | Delegation | Permission grant between two parties | | `adl_` | Agent Delegation | Links an agent to a delegation | | `adi_` | Agent Delegation Invitation | Invitation for a customer to authorize an agent | | `apy_` | API Key | Credential for API access | | `agk_` | Agent Key | Credential bound to one agent | | `wal_` | Wallet | Holds funds for a party | | `eac_` | External Account | Linked bank account for deposits and withdrawals | | `pay_` | Payment | A payment between two parties | | `prq_` | Payment Request | A request for a payment from another party | | `trf_` | Transfer | A deposit or withdrawal between a wallet and a bank account | | `txn_` | Transaction | Ledger entry returned by GET /transactions | | `inv_` | Party Invitation | Pending invitation for a party to join the platform | | `apr_` | Approval Request | A payment awaiting approval | | `req_` | Request | Per-request correlation ID (`req_` plus 12 hex characters), returned as `meta.supportId` on errors | | `whk_` | Webhook | Webhook configuration | | `evt_` | Event | Webhook event payload | ## Working with IDs ### API requests Always use the full prefixed ID in API requests: ```json theme={null} { "data": { "attributes": { "amount": 500000, "counterparty": { "type": "party_id", "value": "pty_019cd1798d617f65a79cb965dda9eac3" }, "customerPartyId": "pty_019cd34e27bf78399b4e75b327d2ab25" } } } ``` ### Validation IDs are validated on every request. An invalid ID is rejected with an `invalid_value` error: ```json theme={null} { "errors": [ { "code": "invalid_value", "status": "422", "detail": "Invalid pty ID format", "source": { "pointer": "/data/attributes/counterparty" } } ] } ``` ## Related * [Parties](/guides/concepts/parties) — Primary business entity using `pty_` IDs * [Agents](/guides/concepts/agents) — AI agents with `agt_` IDs # About the Natural API Source: https://docs.natural.com/api-reference/about Understanding the structure of Natural API requests and responses The Natural API is REST-based and follows a simplified [JSON:API](https://jsonapi.org/) structure. REST requests authenticate with a Natural API key in the `Authorization: Bearer` header. See [Authentication](/api-reference/authentication). The same key works across the [SDKs](/guides/platform/sdks), the [CLI](/guides/platform/cli), and MCP fallback paths. For AI hosts operating Natural for a user, prefer the [MCP connector](/guides/platform/mcp) with OAuth. ## Requests To create or update a resource, wrap your fields in `data.attributes`. No `type` or `id` needed; the type is inferred from the endpoint and the ID is assigned by the server. ### Creating a resource Send a `POST` request with a `data` object containing `attributes`: ```json theme={null} // POST /payments { "data": { "attributes": { "amount": 500000, "currency": "USD", "counterparty": { "type": "email", "value": "carrier@example.com" }, "customerPartyId": "pty_019cd1798d617f65a79cb965dda9eac3", "description": "Payment for Q4 2025 development work" } } } ``` ### Updating a resource To update a resource, send a `PATCH` request with only the fields you want to change inside `attributes`. Omitted fields remain unchanged. ```json theme={null} // PATCH /agents/{agentId} { "data": { "attributes": { "name": "Carrier Payment Agent v3.0" } } } ``` ## Responses Responses follow the [JSON:API specification](https://jsonapi.org/). A response contains a single resource or a list of resources, and each resource has: * **`type`** — A string identifying the kind of resource (e.g., `"agent"`, `"payment"`, `"transaction"`) * **`id`** — A unique [prefixed identifier](/api-reference/IDs) (e.g., `agt_019cd179...`, `pay_019cd179...`) * **`attributes`** — The resource's data fields * **`relationships`** *(optional)* — Links to related resources ### Single resource Responses for a single resource wrap the resource in a `data` object: ```json theme={null} { "data": { "type": "agent", "id": "agt_019cd1798d637a4da75dce386343931d", "attributes": { "name": "Carrier Payment Agent v2.1", "description": "Autonomous agent that pays delivery carriers", "handle": "@natural-carrier_payments", "status": "ACTIVE", "limits": { "perTransaction": 100000 }, "createdAt": "2026-01-04T15:30:00Z", "createdBy": "usr_019cd1798d657de5b5fed4198cb9fac0", "lastActiveAt": "2026-01-05T09:12:00Z" }, "relationships": { "party": { "data": { "type": "party", "id": "pty_019cd1798d617f65a79cb965dda9eac3" } } } } } ``` ### List of resources List endpoints return an array of resources in `data`, with pagination metadata in `meta`: ```json theme={null} { "data": [ { "type": "transaction", "id": "txn_019cd1798d6672a7b828eee780291b72", "attributes": { "amount": 100000, "currency": "USD", "status": "COMPLETED", "transactionType": "payment", "direction": "OUTBOUND", "description": "Payment for Q4 2025 development work", "createdAt": "2026-01-04T15:30:00Z", "updatedAt": "2026-01-04T15:31:00Z", "expectedAvailableAt": null }, "relationships": { "sourceParty": { "data": { "type": "party", "id": "pty_019cd1798d617f65a79cb965dda9eac3" } }, "destinationParty": { "data": { "type": "party", "id": "pty_019cd1798d627ad9bc302511c4f2c115" } } } } ], "meta": { "pagination": { "hasMore": false, "nextCursor": null } } } ``` Use the `nextCursor` value from `meta.pagination` to fetch subsequent pages. ## Relationships Relationships connect resources to each other without nesting the full related resource. Each relationship contains a `data` object with the related resource's `type` and `id`. Relationships appear in **responses**. In requests, reference related resources by including their ID directly in `attributes` (e.g., `customerPartyId`); the one exception is [`POST /agent-keys`](/api-reference/agent-keys/create-agent-key), which takes its agent as a request relationship. ### To-one relationships A to-one relationship links to a single related resource: ```json theme={null} "sender": { "data": { "type": "party", "id": "pty_019cd1798d617f65a79cb965dda9eac3" } } ``` A `null` value in `data` indicates the relationship is absent: ```json theme={null} "sourceParty": { "data": null } ``` ### Common relationships | Relationship | Description | Found on | | ------------------ | -------------------------------------- | --------------- | | `party` | Owning party | Agents, Wallets | | `sender` | Party that initiated the payment | Payments | | `recipient` | Recipient party for the payment | Payments | | `sourceParty` | Originating party | Transactions | | `destinationParty` | Receiving party | Transactions | | `payment` | Payment that produced the transaction | Transactions | | `transfer` | Transfer that produced the transaction | Transactions | ### Using relationship IDs Use the `type` and `id` from a relationship to fetch the related resource. For example, if a payment response includes: ```json theme={null} "relationships": { "recipient": { "data": { "type": "party", "id": "pty_019cd1798d617f65a79cb965dda9eac3" } } } ``` You can look up the party using its ID in a subsequent request. ## Glossary * **Resource** — An entity in the Natural API, such as a payment, agent, or transaction. * **Attribute** — A piece of information about a resource (e.g., `status`, `amount`, `createdAt`). * **Relationship** — A link from one resource to another, represented by `type` and `id`. # Create agent key Source: https://docs.natural.com/api-reference/agent-keys/create-agent-key /api-reference/openapi.json post /agent-keys Create an agent key for an agent. The secret is returned only once. # List agent keys Source: https://docs.natural.com/api-reference/agent-keys/list-agent-keys /api-reference/openapi.json get /agent-keys List agent keys # Revoke agent key Source: https://docs.natural.com/api-reference/agent-keys/revoke-agent-key /api-reference/openapi.json delete /agent-keys/{keyId} Revoke an agent key immediately without issuing a replacement. Other active keys for the same agent keep working. # Rotate agent key Source: https://docs.natural.com/api-reference/agent-keys/rotate-agent-key /api-reference/openapi.json post /agent-keys/{keyId}/rotate Generate a replacement agent key. # Create agent Source: https://docs.natural.com/api-reference/agents/create-agent /api-reference/openapi.json post /agents Create an agent # Delete agent Source: https://docs.natural.com/api-reference/agents/delete-agent /api-reference/openapi.json delete /agents/{agentId} Delete an agent and revoke its active customer authorizations and pending invitations # Get agent Source: https://docs.natural.com/api-reference/agents/get-agent /api-reference/openapi.json get /agents/{agentId} Get an agent # List agents Source: https://docs.natural.com/api-reference/agents/list-agents /api-reference/openapi.json get /agents List agents # Update agent Source: https://docs.natural.com/api-reference/agents/update-agent /api-reference/openapi.json patch /agents/{agentId} Update an agent's mutable fields; a slug renames the handle and can never be cleared # Create API key Source: https://docs.natural.com/api-reference/api-keys/create-api-key /api-reference/openapi.json post /api-keys Create an API key. The secret is returned only once. # Get API key Source: https://docs.natural.com/api-reference/api-keys/get-api-key /api-reference/openapi.json get /api-keys/{keyId} Get an API key # List API keys Source: https://docs.natural.com/api-reference/api-keys/list-api-keys /api-reference/openapi.json get /api-keys List API keys # Revoke API key Source: https://docs.natural.com/api-reference/api-keys/revoke-api-key /api-reference/openapi.json delete /api-keys/{keyId} Revoke an API key # Approve payment or transfer Source: https://docs.natural.com/api-reference/approvals/approve-payment-or-transfer /api-reference/openapi.json post /approvals/{approvalId}/approve Approve the payment or transfer under review # Deny payment or transfer Source: https://docs.natural.com/api-reference/approvals/deny-payment-or-transfer /api-reference/openapi.json post /approvals/{approvalId}/deny Deny the payment or transfer under review # Get approval Source: https://docs.natural.com/api-reference/approvals/get-approval /api-reference/openapi.json get /approvals/{approvalId} Get an approval # List approvals Source: https://docs.natural.com/api-reference/approvals/list-approvals /api-reference/openapi.json get /approvals List approvals # Authentication Source: https://docs.natural.com/api-reference/authentication API keys, agent keys, OAuth, and Bearer authentication The Natural API uses Bearer authentication. Include your credential in the `Authorization` header of every request: ```bash theme={null} curl https://api.natural.com/payments \ -H "Authorization: Bearer sk_ntl_prod_abc123..." ``` The same credential authenticates the [SDKs](/guides/platform/sdks), the [CLI](/guides/platform/cli), and REST calls. AI hosts using the [MCP connector](/guides/platform/mcp) authenticate with browser OAuth by default. ## Credential modes Natural supports four credential modes. They differ in **who the request acts as** and **how agent identity is established**: | Mode | Prefix / flow | Acts as | | ---------------------------------------------- | --------------------- | ------------------------------- | | [API key](#api-keys) | `sk_ntl_…` | Your party | | [Agent key](#agent-keys) | `ak_ntl_…` | One specific agent, verified | | [User-scoped MCP OAuth](/guides/platform/mcp) | Browser OAuth consent | The authorizing user | | [Agent-scoped MCP OAuth](/guides/platform/mcp) | Browser OAuth consent | Selected or new agent, verified | Key rules: * **Bound credentials carry verified agent identity.** With an agent key or agent-scoped OAuth grant, the server resolves the agent from the credential itself. * **User-scoped money movement is valid.** Dashboard users, user-scoped MCP grants, and party API keys can move money without any agent attribution. * **Agent-attributed money movement requires `X-Instance-ID`.** See [instance attribution](#instance-attribution-for-agent-money-movement). ## API keys API keys are party-scoped credentials in the format `sk_ntl_{environment}_{secret}`: | Prefix | Environment | | -------------- | ----------- | | `sk_ntl_prod_` | Production | The production base URL is `https://api.natural.com`. An API key acts as your party. For new agent integrations, use an agent key or agent-scoped OAuth instead of party-key attribution. ### Creating API keys Create API keys from the [Natural Dashboard](https://natural.com/login) or via [`POST /api-keys`](/api-reference/api-keys/create-api-key). The key secret is shown once; store it immediately. Each key can be scoped to a subset of permissions. Scope a key down to exactly what the integration needs, for example a read-only key or one limited to payments: ```json theme={null} { "data": { "attributes": { "name": "Carrier Payment Agent", "scopes": ["agents.read", "payments.create", "payments.read"] } } } ``` ## Agent keys Agent keys are credentials bound to exactly one of your [agents](/guides/concepts/agents), in the format `ak_ntl_{environment}_{secret}`. Requests authenticated with an agent key resolve as that agent, verified by the credential itself. ```bash theme={null} # Acts as the bound agent curl https://api.natural.com/identity/me \ -H "Authorization: Bearer ak_ntl_prod_abc123..." ``` Agent keys work everywhere API keys work: SDKs, CLI, MCP fallback, and REST. ### Creating agent keys Create agent keys from the dashboard or via [`POST /agent-keys`](/api-reference/agent-keys/create-agent-key), naming the existing agent it is bound to: ```json theme={null} { "data": { "attributes": {}, "relationships": { "agent": { "data": { "type": "agent", "id": "agt_019cd1798d637a4da75dce386343931d" } } } } } ``` The create response includes the full secret in `attributes.agentKey` exactly once. List and revoke responses only include the non-secret `agentKeyPrefix`. ### Agent key permissions Agent keys do not take user-selected scopes. They always receive Natural's server-defined agent credential policy: broad operational capability for the bound agent (payments, payment requests, transfers, wallet reads, customer reads) with hard exclusions that no agent credential can ever hold: * Creating agents * Creating, listing, or revoking API keys or agent keys * Team membership and account control * Party profile/admin changes * Wallet lifecycle/admin operations * Vault funds Agent-scoped MCP OAuth grants are clamped by the same policy. ### Rotation [`POST /agent-keys/{keyId}/rotate`](/api-reference/agent-keys/rotate-agent-key) issues a replacement while the old key stays valid for a grace period you choose, up to 24 hours. Multiple active keys per agent are valid, so a running deployment never loses access mid-rotation. ## Instance attribution for agent money movement Agent-attributed money movement (payments, transfers, payment-request fulfillment) requires an `X-Instance-ID` header — a caller-chosen identifier for the agent run making the call. Without it, the request is rejected with `400 missing_instance_id`. Reads don't require it, and user-scoped requests (dashboard, user-scoped MCP, and plain API keys) are never agent-attributed. In the SDKs, pass the instance ID when constructing the client; it is sent as `X-Instance-ID` on every request: ```python theme={null} from naturalpay import Natural client = Natural(instance_id="invoice-run-1234") ``` ## MCP OAuth The [MCP connector](/guides/platform/mcp) signs AI hosts in with browser OAuth. On the consent screen, you pick who the connection acts as: * **As an agent (agent-scoped)** — The default. Pick an existing agent or create a new one during approval. Tool calls then run as that agent, with the agent's clamped permissions, and can't create agents or manage keys. * **As me (user-scoped)** — Tool calls run as you, with your party's permissions. You get this when you approve without picking an agent. To switch modes, reconnect and choose again. ## Security * Store keys in a dedicated secret management system. Never commit them to source control. Both `sk_ntl_` and `ak_ntl_` prefixes should be treated as secrets by your scanners. * Rotate keys periodically. You can have multiple active keys to enable zero-downtime rotation. * Revoke compromised keys immediately via the dashboard, [`DELETE /api-keys/{keyId}`](/api-reference/api-keys/revoke-api-key), or [`DELETE /agent-keys/{keyId}`](/api-reference/agent-keys/revoke-agent-key). * All requests require HTTPS. ## Related * [Agents](/guides/concepts/agents) — The agent model, instances, and audit trail * [MCP](/guides/platform/mcp) — Connect Claude, Cursor, and other AI hosts to Natural * [API keys](/guides/concepts/api-keys) — Create, list, and revoke keys * [Error Handling](/api-reference/errors/error-handling) — Authentication error codes # Backwards compatibility Source: https://docs.natural.com/api-reference/backwards-compatibility Our commitment to API stability The Natural API is unversioned. There is one version, and we do not make backwards-incompatible changes to existing endpoints. The API evolves through additive, non-breaking changes only. ## Backwards-compatible changes These changes can happen at any time without notice: * Adding new API endpoints * Adding new optional request parameters to existing endpoints * Adding new fields to response objects * Adding new values to existing enums (e.g., new payment statuses) * Adding new error codes * Changing the order of fields in responses * Changing the length or format of opaque strings (IDs, cursors, tokens) ## Breaking changes These require advance notice and a migration path: * Removing or renaming existing endpoints * Removing or renaming response fields * Changing the type of an existing field * Making a previously optional parameter required * Changing the meaning of an existing field or parameter * Removing supported values from enums * Changing authentication mechanisms * Changing error response structure ## Writing resilient integrations * **Ignore unknown fields** in responses. New fields may be added at any time. * **Handle unknown enum values** gracefully. New statuses or types may appear. * **Don't hard-code cursor formats.** Treat cursors as opaque strings. * **Use idempotency keys** for payment operations to safely retry on failure. ## Related * [Idempotency](/api-reference/idempotency) — Safe retries * [Error Handling](/api-reference/errors/error-handling) — Error response structure # Get customer Source: https://docs.natural.com/api-reference/customers/get-customer /api-reference/openapi.json get /customers/{customerId} Get a customer who has authorized an agent # Invite customers Source: https://docs.natural.com/api-reference/customers/invite-customers /api-reference/openapi.json post /customers/invitations Invite specific customers by email or phone to approve a set of your agents, each with its own permissions and limits # List customer invitations Source: https://docs.natural.com/api-reference/customers/list-customer-invitations /api-reference/openapi.json get /customers/invitations List pending invitations addressed to specific customers # List customers Source: https://docs.natural.com/api-reference/customers/list-customers /api-reference/openapi.json get /customers List customers who have authorized an agent to act for them # Revoke agent access Source: https://docs.natural.com/api-reference/customers/revoke-agent-access /api-reference/openapi.json delete /customers/{customerId}/agents/{agentId} Remove an agent's access to a customer # Revoke customer invitation Source: https://docs.natural.com/api-reference/customers/revoke-customer-invitation /api-reference/openapi.json delete /customers/invitations/{invitationId} Revoke a pending invitation addressed to a specific customer, so it can no longer be accepted # Agent keys Source: https://docs.natural.com/api-reference/errors/agent-keys Public error codes for Agent keys API endpoints All error responses follow the standard [error format](/api-reference/errors/error-handling). Validation errors (422) and rate limit errors (429) apply to all endpoints and are documented in the [Error Handling guide](/api-reference/errors/error-handling). | Code | Status | Detail | | --------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- | | `invalid_value` | 400 | The information you entered isn't valid. Please check it and try again. | | `unauthenticated` | 401 | Authentication is required. | | `forbidden` | 403 | You do not have permission to perform this action. | | `agent_key_not_found` | 404 | Agent key not found. | | `agent_not_active` | 409 | The agent is not active. | | `already_exists` | 409 | The resource already exists. | | `conflict` | 409 | The request conflicts with the current resource state. | | `rate_limited` | 429 | Too many requests. Please try again later. | | `server_error` | 500 | Something went wrong. | | `bad_gateway` | 502 | We couldn't complete that request because one of Natural's services returned an unexpected response. Please try again. | | `service_unavailable` | 503 | The service is temporarily unavailable. | # Agents Source: https://docs.natural.com/api-reference/errors/agents Public error codes for Agents API endpoints All error responses follow the standard [error format](/api-reference/errors/error-handling). Validation errors (422) and rate limit errors (429) apply to all endpoints and are documented in the [Error Handling guide](/api-reference/errors/error-handling). | Code | Status | Detail | | ------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `invalid_value` | 400 | The information you entered isn't valid. Please check it and try again. | | `invitation_self_not_allowed` | 400 | You can't invite yourself. Enter a different customer's email or phone number. | | `unauthenticated` | 401 | Authentication is required. | | `forbidden` | 403 | You do not have permission to perform this action. | | `handle_human_session_required` | 403 | Agent handle slugs can only be set from a user session. | | `party_not_active` | 403 | Only active accounts can change handles. | | `party_not_verified` | 403 | Your account must be verified before it can change handles. | | `agent_not_found` | 404 | Agent not found. | | `already_exists` | 409 | The resource already exists. | | `conflict` | 409 | The request conflicts with the current resource state. | | `namespace_unavailable` | 409 | This handle is not available. | | `slug_unavailable` | 409 | This handle is not available. | | `namespace_invalid` | 422 | That handle is reserved or contains unsupported characters. Try another one. | | `slug_invalid` | 422 | This agent slug is not valid. Use 1-30 lowercase letters, digits, or interior dots or underscores; hyphen is reserved as the party/agent separator and reserved words are rejected. | | `rate_limited` | 429 | Too many requests. Please try again later. | | `server_error` | 500 | Something went wrong. | | `bad_gateway` | 502 | We couldn't complete that request because one of Natural's services returned an unexpected response. Please try again. | | `service_unavailable` | 503 | The service is temporarily unavailable. | # API keys Source: https://docs.natural.com/api-reference/errors/api-keys Public error codes for API keys API endpoints All error responses follow the standard [error format](/api-reference/errors/error-handling). Validation errors (422) and rate limit errors (429) apply to all endpoints and are documented in the [Error Handling guide](/api-reference/errors/error-handling). | Code | Status | Detail | | --------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- | | `invalid_value` | 400 | The information you entered isn't valid. Please check it and try again. | | `unauthenticated` | 401 | Authentication is required. | | `forbidden` | 403 | You do not have permission to perform this action. | | `api_key_not_found` | 404 | API key not found. | | `already_exists` | 409 | The resource already exists. | | `conflict` | 409 | The request conflicts with the current resource state. | | `rate_limited` | 429 | Too many requests. Please try again later. | | `server_error` | 500 | Something went wrong. | | `bad_gateway` | 502 | We couldn't complete that request because one of Natural's services returned an unexpected response. Please try again. | | `service_unavailable` | 503 | The service is temporarily unavailable. | # Approvals Source: https://docs.natural.com/api-reference/errors/approvals Public error codes for Approvals API endpoints All error responses follow the standard [error format](/api-reference/errors/error-handling). Validation errors (422) and rate limit errors (429) apply to all endpoints and are documented in the [Error Handling guide](/api-reference/errors/error-handling). | Code | Status | Detail | | --------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- | | `invalid_value` | 400 | The information you entered isn't valid. Please check it and try again. | | `unauthenticated` | 401 | Authentication is required. | | `forbidden` | 403 | You do not have permission to perform this action. | | `approval_not_found` | 404 | Approval not found. | | `already_exists` | 409 | The resource already exists. | | `conflict` | 409 | The request conflicts with the current resource state. | | `rate_limited` | 429 | Too many requests. Please try again later. | | `server_error` | 500 | Something went wrong. | | `bad_gateway` | 502 | We couldn't complete that request because one of Natural's services returned an unexpected response. Please try again. | | `service_unavailable` | 503 | The service is temporarily unavailable. | # Customers Source: https://docs.natural.com/api-reference/errors/customers Public error codes for Customers API endpoints All error responses follow the standard [error format](/api-reference/errors/error-handling). Validation errors (422) and rate limit errors (429) apply to all endpoints and are documented in the [Error Handling guide](/api-reference/errors/error-handling). | Code | Status | Detail | | ------------------------------------------------------ | ------ | ---------------------------------------------------------------------------------------------------------------------- | | `customer_invitation_recipient_unsupported_in_sandbox` | 400 | Email and phone recipients aren't supported in Sandbox. Use POST /simulations/invite-customer. | | `invalid_value` | 400 | The information you entered isn't valid. Please check it and try again. | | `unauthenticated` | 401 | Authentication is required. | | `forbidden` | 403 | You do not have permission to perform this action. | | `customer_not_found` | 404 | Customer not found. | | `already_exists` | 409 | The resource already exists. | | `conflict` | 409 | The request conflicts with the current resource state. | | `rate_limited` | 429 | Too many requests. Please try again later. | | `server_error` | 500 | Something went wrong. | | `bad_gateway` | 502 | We couldn't complete that request because one of Natural's services returned an unexpected response. Please try again. | | `service_unavailable` | 503 | The service is temporarily unavailable. | # Error handling Source: https://docs.natural.com/api-reference/errors/error-handling Standard error format and recovery patterns All Natural API errors follow a standard JSON:API-style format with stable public codes and safe display copy. ## Error response format Every error response contains an `errors` array with one or more error objects: ```json theme={null} { "errors": [ { "code": "insufficient_funds", "detail": "Insufficient funds.", "status": "409", "meta": { "supportId": "req_a1b2c3d4e5f6" } } ] } ``` Each error object contains: | Field | Type | Required | Description | | -------- | ------ | -------- | ---------------------------------------------------------------------- | | `code` | string | Yes | Stable lower-snake-case public error code (e.g., `insufficient_funds`) | | `detail` | string | Yes | Safe user-facing error message | | `status` | string | Yes | HTTP status code as a string | | `source` | object | No | Location of the invalid request value, such as `source.pointer` | | `meta` | object | Yes | Metadata containing `supportId` for troubleshooting | ### Metadata Public error responses include `meta.supportId` by default. Internal/upstream metadata is kept in logs and traces, correlated by `supportId`. The exception is external account errors that can be repaired by relinking: those may also include `meta.connectionStatus` (`login_required` or `disconnected`) and `meta.provider` with the provider's error code, type, and request ID. ## Rate limit errors Requests over the rate limit receive a `429` with code `rate_limited`. The response carries a `Retry-After` header with the seconds to wait before retrying. See [Rate limits](/api-reference/rate-limits). ## Validation errors Validation errors may include `source.pointer` for API clients: ```json theme={null} { "errors": [ { "code": "invalid_value", "detail": "The information you entered isn't valid. Please check it and try again.", "status": "422", "source": { "pointer": "/data/attributes/email" }, "meta": { "supportId": "req_a1b2c3d4e5f6" } } ] } ``` ## Cross-cutting errors These errors depend on how a request is authenticated and attributed rather than on the resource being called, so the per-resource error pages do not list them. Most apply to requests made with agent credentials or on behalf of another party. | Code | Status | Detail | | ------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `missing_instance_id` | 400 | X-Instance-ID is required for agent-attributed money movement. Send the X-Instance-ID header with a caller-chosen run/session identifier for this agent. | | `account_closed` | 403 | This account has been closed. Contact support for assistance. | | `account_frozen` | 403 | This account is temporarily frozen. Contact support for assistance. | | `agent_id_conflict` | 403 | X-Agent-ID conflicts with the agent bound to this credential. Remove the X-Agent-ID header — bound credentials carry verified agent identity. | | `agent_wallet_access_no_access` | 403 | The agent does not have access to the selected wallet. | | `delegation_required` | 403 | Acting on behalf of another party requires a valid delegation. For API key callers, include X-Agent-ID and X-Instance-ID for an authorized agent. | | `agent_wallet_access_wallet_required` | 422 | A wallet is required for this agent action. | # Events Source: https://docs.natural.com/api-reference/errors/events Public error codes for Events API endpoints All error responses follow the standard [error format](/api-reference/errors/error-handling). Validation errors (422) and rate limit errors (429) apply to all endpoints and are documented in the [Error Handling guide](/api-reference/errors/error-handling). | Code | Status | Detail | | --------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- | | `invalid_value` | 400 | The information you entered isn't valid. Please check it and try again. | | `unauthenticated` | 401 | Authentication is required. | | `forbidden` | 403 | You do not have permission to perform this action. | | `event_not_found` | 404 | Event not found. | | `already_exists` | 409 | The resource already exists. | | `conflict` | 409 | The request conflicts with the current resource state. | | `rate_limited` | 429 | Too many requests. Please try again later. | | `server_error` | 500 | Something went wrong. | | `bad_gateway` | 502 | We couldn't complete that request because one of Natural's services returned an unexpected response. Please try again. | | `service_unavailable` | 503 | The service is temporarily unavailable. | # External Accounts Source: https://docs.natural.com/api-reference/errors/external-accounts Public error codes for External Accounts API endpoints All error responses follow the standard [error format](/api-reference/errors/error-handling). Validation errors (422) and rate limit errors (429) apply to all endpoints and are documented in the [Error Handling guide](/api-reference/errors/error-handling). | Code | Status | Detail | | -------------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- | | `external_account_auth_consent_required` | 400 | Additional Plaid Auth consent is required before this bank account can be linked. | | `external_account_auth_data_not_ready` | 400 | Plaid Auth data is not ready for this bank account. Try again later. | | `external_account_auth_permission_required` | 400 | Plaid Auth permission is required before this bank account can be linked. | | `external_account_institution_unavailable` | 400 | The financial institution is temporarily unavailable. Try again later or use another bank account. | | `external_account_link_failed` | 400 | Unable to link this bank account. | | `external_account_no_auth_accounts` | 400 | No eligible checking, savings, or cash management account is available to link. | | `external_account_no_eligible_accounts` | 400 | No eligible bank accounts are available to link. | | `external_account_not_supported` | 400 | This bank account is not supported for linking. | | `invalid_value` | 400 | The information you entered isn't valid. Please check it and try again. | | `unauthenticated` | 401 | Authentication is required. | | `forbidden` | 403 | You do not have permission to perform this action. | | `external_account_not_found` | 404 | External account not found. | | `already_exists` | 409 | The resource already exists. | | `bank_account_already_linked` | 409 | This bank account is already linked. | | `conflict` | 409 | The request conflicts with the current resource state. | | `external_account_connection_disconnected` | 409 | External account connection is disconnected. | | `external_account_connection_login_required` | 409 | External account connection requires reauthentication. | | `rate_limited` | 429 | Too many requests. Please try again later. | | `server_error` | 500 | Something went wrong. | | `bad_gateway` | 502 | We couldn't complete that request because one of Natural's services returned an unexpected response. Please try again. | | `service_unavailable` | 503 | The service is temporarily unavailable. | # Parties Source: https://docs.natural.com/api-reference/errors/parties Public error codes for Parties API endpoints All error responses follow the standard [error format](/api-reference/errors/error-handling). Validation errors (422) and rate limit errors (429) apply to all endpoints and are documented in the [Error Handling guide](/api-reference/errors/error-handling). | Code | Status | Detail | | ------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `invalid_value` | 400 | The information you entered isn't valid. Please check it and try again. | | `unauthenticated` | 401 | Authentication is required. | | `forbidden` | 403 | You do not have permission to perform this action. | | `handle_human_session_required` | 403 | Agent handle slugs can only be set from a user session. | | `party_not_active` | 403 | Only active accounts can change handles. | | `party_not_verified` | 403 | Your account must be verified before it can change handles. | | `party_not_found` | 404 | Party not found. | | `already_exists` | 409 | The resource already exists. | | `conflict` | 409 | The request conflicts with the current resource state. | | `ein_already_exists` | 409 | This EIN is already associated with another account. | | `namespace_unavailable` | 409 | This handle is not available. | | `slug_unavailable` | 409 | This handle is not available. | | `ssn_already_exists` | 409 | This SSN is already associated with another account. | | `namespace_invalid` | 422 | That handle is reserved or contains unsupported characters. Try another one. | | `slug_invalid` | 422 | This agent slug is not valid. Use 1-30 lowercase letters, digits, or interior dots or underscores; hyphen is reserved as the party/agent separator and reserved words are rejected. | | `rate_limited` | 429 | Too many requests. Please try again later. | | `server_error` | 500 | Something went wrong. | | `bad_gateway` | 502 | We couldn't complete that request because one of Natural's services returned an unexpected response. Please try again. | | `service_unavailable` | 503 | The service is temporarily unavailable. | # Party Invitations Source: https://docs.natural.com/api-reference/errors/party-invitations Public error codes for Party Invitations API endpoints All error responses follow the standard [error format](/api-reference/errors/error-handling). Validation errors (422) and rate limit errors (429) apply to all endpoints and are documented in the [Error Handling guide](/api-reference/errors/error-handling). | Code | Status | Detail | | ---------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- | | `invalid_value` | 400 | The information you entered isn't valid. Please check it and try again. | | `unauthenticated` | 401 | Authentication is required. | | `forbidden` | 403 | You do not have permission to perform this action. | | `invitation_not_found` | 404 | Invitation not found. | | `already_exists` | 409 | The resource already exists. | | `conflict` | 409 | The request conflicts with the current resource state. | | `rate_limited` | 429 | Too many requests. Please try again later. | | `server_error` | 500 | Something went wrong. | | `bad_gateway` | 502 | We couldn't complete that request because one of Natural's services returned an unexpected response. Please try again. | | `service_unavailable` | 503 | The service is temporarily unavailable. | # Payment Requests Source: https://docs.natural.com/api-reference/errors/payment-requests Public error codes for Payment Requests API endpoints All error responses follow the standard [error format](/api-reference/errors/error-handling). Validation errors (422) and rate limit errors (429) apply to all endpoints and are documented in the [Error Handling guide](/api-reference/errors/error-handling). | Code | Status | Detail | | --------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------ | | `agent_payer_not_notifiable` | 400 | Cannot resend a payment request notification to an agent payer — agents receive in-app webhooks only. | | `invalid_pagination_cursor` | 400 | Invalid pagination cursor. | | `invalid_value` | 400 | The information you entered isn't valid. Please check it and try again. | | `no_payer_contact` | 400 | Payment request has no contact on file to resend the notification to. | | `unauthenticated` | 401 | Authentication is required. | | `forbidden` | 403 | You do not have permission to perform this action. | | `linked_account_not_found` | 404 | Linked bank account not found. | | `payer_agent_not_found` | 404 | The specified payer agent does not exist. | | `payer_handle_not_found` | 404 | No account was found for the specified handle. | | `payer_not_found` | 404 | Payer not found. | | `payment_request_not_found` | 404 | Payment request not found. | | `already_exists` | 409 | The resource already exists. | | `conflict` | 409 | The request conflicts with the current resource state. | | `wallet_closed` | 409 | This wallet is closed. | | `wallet_not_active` | 409 | You cannot request payments into a frozen wallet. | | `payer_agent_not_eligible` | 422 | The specified payer agent cannot be charged for wallet-backed payments. | | `payer_handle_invalid` | 422 | The specified handle is not a valid handle. | | `sandbox_unknown_recipient_unsupported` | 422 | Unknown recipients are not supported in sandbox. Send payments to an existing sandbox party or create a sandbox test customer. | | `rate_limited` | 429 | Too many requests. Please try again later. | | `server_error` | 500 | Something went wrong. | | `bad_gateway` | 502 | We couldn't complete that request because one of Natural's services returned an unexpected response. Please try again. | | `service_unavailable` | 503 | The service is temporarily unavailable. | # Payments Source: https://docs.natural.com/api-reference/errors/payments Public error codes for Payments API endpoints All error responses follow the standard [error format](/api-reference/errors/error-handling). Validation errors (422) and rate limit errors (429) apply to all endpoints and are documented in the [Error Handling guide](/api-reference/errors/error-handling). | Code | Status | Detail | | --------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------ | | `invalid_value` | 400 | The information you entered isn't valid. Please check it and try again. | | `unauthenticated` | 401 | Authentication is required. | | `forbidden` | 403 | You do not have permission to perform this action. | | `external_account_not_found` | 404 | External account not found. | | `payment_not_found` | 404 | Payment not found. | | `recipient_agent_not_found` | 404 | The specified recipient agent does not exist. | | `recipient_handle_not_found` | 404 | No account was found for the specified handle. | | `recipient_not_found` | 404 | Recipient not found. | | `already_exists` | 409 | The resource already exists. | | `conflict` | 409 | The request conflicts with the current resource state. | | `customer_wallet_required` | 409 | A customer wallet is required before creating this payment. | | `external_account_not_verified` | 409 | External account must be verified before it can be used. | | `insufficient_funds` | 409 | Insufficient funds. | | `payment_failed` | 409 | Payment failed. | | `recipient_agent_not_eligible` | 422 | The specified recipient agent is not eligible to receive wallet-backed payments. | | `recipient_handle_invalid` | 422 | The specified handle is not a valid handle. | | `recipient_not_eligible` | 422 | The selected recipient is not yet eligible to receive wallet-backed payments. | | `sandbox_unknown_recipient_unsupported` | 422 | Unknown recipients are not supported in sandbox. Send payments to an existing sandbox party or create a sandbox test customer. | | `rate_limited` | 429 | Too many requests. Please try again later. | | `server_error` | 500 | Something went wrong. | | `bad_gateway` | 502 | We couldn't complete that request because one of Natural's services returned an unexpected response. Please try again. | | `service_unavailable` | 503 | The service is temporarily unavailable. | # Transactions Source: https://docs.natural.com/api-reference/errors/transactions Public error codes for Transactions API endpoints All error responses follow the standard [error format](/api-reference/errors/error-handling). Validation errors (422) and rate limit errors (429) apply to all endpoints and are documented in the [Error Handling guide](/api-reference/errors/error-handling). | Code | Status | Detail | | --------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- | | `invalid_pagination_cursor` | 400 | Invalid pagination cursor. | | `invalid_value` | 400 | The information you entered isn't valid. Please check it and try again. | | `unauthenticated` | 401 | Authentication is required. | | `forbidden` | 403 | You do not have permission to perform this action. | | `transaction_not_found` | 404 | Transaction not found. | | `already_exists` | 409 | The resource already exists. | | `conflict` | 409 | The request conflicts with the current resource state. | | `rate_limited` | 429 | Too many requests. Please try again later. | | `server_error` | 500 | Something went wrong. | | `bad_gateway` | 502 | We couldn't complete that request because one of Natural's services returned an unexpected response. Please try again. | | `service_unavailable` | 503 | The service is temporarily unavailable. | # Transfers Source: https://docs.natural.com/api-reference/errors/transfers Public error codes for Transfers API endpoints All error responses follow the standard [error format](/api-reference/errors/error-handling). Validation errors (422) and rate limit errors (429) apply to all endpoints and are documented in the [Error Handling guide](/api-reference/errors/error-handling). | Code | Status | Detail | | --------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `invalid_pagination_cursor` | 400 | Invalid pagination cursor. | | `invalid_value` | 400 | The information you entered isn't valid. Please check it and try again. | | `unauthenticated` | 401 | Authentication is required. | | `forbidden` | 403 | You do not have permission to perform this action. | | `instant_withdrawals_not_enabled` | 403 | Instant withdrawals are not available for this Natural account. | | `wire_withdrawals_not_enabled` | 403 | Wire withdrawals are not available for this Natural account. | | `external_account_not_found` | 404 | External account not found. | | `multiwallet_not_enabled` | 404 | Multiwallet is not enabled. | | `transfer_not_found` | 404 | Transfer not found. | | `wallet_not_found` | 404 | Wallet not found. | | `already_exists` | 409 | The resource already exists. | | `conflict` | 409 | The request conflicts with the current resource state. | | `external_account_connection_disconnected` | 409 | External account connection is disconnected. | | `external_account_connection_login_required` | 409 | External account connection requires reauthentication. | | `external_account_not_verified` | 409 | External account must be verified before it can be used. | | `insufficient_funds` | 409 | Insufficient funds. | | `insufficient_funds` | 409 | The linked bank account has insufficient funds for this transfer. | | `transfer_failed` | 409 | Transfer failed. | | `deposit_balance_unverified_limit_exceeded` | 422 | Deposits are limited to \$1,000 per day until your bank account balance can be verified. Reconnect your bank account to restore your full limit. | | `deposit_tier_daily_pull_limit_exceeded` | 422 | This deposit exceeds the remaining daily bank-pull limit. Send a wire directly to your wallet to move the full amount today. | | `instant_withdrawal_destination_not_eligible` | 422 | This bank account is not eligible for instant withdrawals. Choose standard delivery instead. | | `transfer_limit_exceeded` | 422 | This transfer exceeds a limit on your account. | | `rate_limited` | 429 | Too many requests. Please try again later. | | `server_error` | 500 | Something went wrong. | | `bad_gateway` | 502 | We couldn't complete that request because one of Natural's services returned an unexpected response. Please try again. | | `service_unavailable` | 503 | The service is temporarily unavailable. | # Wallets Source: https://docs.natural.com/api-reference/errors/wallets Public error codes for Wallets API endpoints All error responses follow the standard [error format](/api-reference/errors/error-handling). Validation errors (422) and rate limit errors (429) apply to all endpoints and are documented in the [Error Handling guide](/api-reference/errors/error-handling). | Code | Status | Detail | | ----------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------- | | `invalid_value` | 400 | The information you entered isn't valid. Please check it and try again. | | `unauthenticated` | 401 | Authentication is required. | | `forbidden` | 403 | You do not have permission to perform this action. | | `external_account_not_found` | 404 | External account not found. | | `multiwallet_not_enabled` | 404 | Multiwallet is not enabled. | | `wallet_not_found` | 404 | Wallet not found. | | `agent_not_active` | 409 | The agent must be active for this operation. | | `already_exists` | 409 | The resource already exists. | | `bank_account_already_linked` | 409 | This bank account is already linked. | | `conflict` | 409 | The request conflicts with the current resource state. | | `default_wallet_cannot_be_detached` | 409 | Make another wallet the agent's default before detaching this wallet. If this is the agent's only wallet, attach another wallet first. | | `default_wallet_cannot_be_frozen` | 409 | Make another active standard wallet the default before freezing this wallet. | | `external_account_not_verified` | 409 | External account must be verified before it can be used. | | `insufficient_funds` | 409 | Insufficient funds. | | `transfer_failed` | 409 | Transfer failed. | | `wallet_closed` | 409 | Closed wallets cannot change freeze status. | | `wallet_frozen_by_natural` | 409 | This wallet was frozen by Natural and cannot be unfrozen through this endpoint. | | `wallet_not_active` | 409 | The wallet must be active for this operation. | | `vault_not_allowed` | 422 | Agents cannot be attached to vault wallets. | | `rate_limited` | 429 | Too many requests. Please try again later. | | `server_error` | 500 | Something went wrong. | | `bad_gateway` | 502 | We couldn't complete that request because one of Natural's services returned an unexpected response. Please try again. | | `service_unavailable` | 503 | The service is temporarily unavailable. | # Webhooks Source: https://docs.natural.com/api-reference/errors/webhooks Public error codes for Webhooks API endpoints All error responses follow the standard [error format](/api-reference/errors/error-handling). Validation errors (422) and rate limit errors (429) apply to all endpoints and are documented in the [Error Handling guide](/api-reference/errors/error-handling). | Code | Status | Detail | | -------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- | | `invalid_idempotency_key` | 400 | The idempotency key is invalid. | | `invalid_redelivery_target` | 400 | The redelivery target is invalid. | | `invalid_value` | 400 | The information you entered isn't valid. Please check it and try again. | | `unauthenticated` | 401 | Authentication is required. | | `forbidden` | 403 | You do not have permission to perform this action. | | `delivery_not_found` | 404 | Webhook delivery not found. | | `event_not_delivered` | 404 | This event was not delivered to the specified webhook. | | `webhook_not_found` | 404 | Webhook not found. | | `already_exists` | 409 | The resource already exists. | | `conflict` | 409 | The request conflicts with the current resource state. | | `delegated_authorization_inactive` | 409 | The original delegation no longer authorizes this event. | | `idempotency_key_conflict` | 409 | This idempotency key was already used for a different redelivery request. | | `redelivery_in_progress` | 409 | A redelivery is already in progress for this event and webhook. | | `redelivery_not_eligible` | 409 | This event cannot be redelivered in its current state. | | `redelivery_window_expired` | 409 | This event is outside the redelivery window. | | `webhook_endpoint_unavailable` | 409 | Enable the webhook endpoint before redelivering this event. | | `rate_limited` | 429 | Too many requests. Please try again later. | | `redelivery_rate_limited` | 429 | Too many redeliveries were requested for this webhook. Try again later. | | `server_error` | 500 | Something went wrong. | | `bad_gateway` | 502 | We couldn't complete that request because one of Natural's services returned an unexpected response. Please try again. | | `delegation_authorization_unavailable` | 503 | Delegation authorization is temporarily unavailable. Try again later. | | `service_unavailable` | 503 | The service is temporarily unavailable. | # Event types Source: https://docs.natural.com/api-reference/event-catalog Complete reference of all webhook event types and their payloads Each event includes a `type` field describing what happened and a `data.object` containing a point-in-time snapshot of the resource. Expand an event to see the full payload your webhook endpoint receives. ## Payments A payment resource was created before approval or money movement submission. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2409", "object": "event", "type": "payment.created", "resourceId": "pay_019cd3444a7a70efaf554fd8450d3403", "resourceType": "payment", "createdAt": "2026-01-15T14:30:00Z", "data": { "object": { "id": "pay_019cd3444a7a70efaf554fd8450d3403", "type": "payment", "senderPartyId": "pty_019cd34e27bf78399b4e75b327d2ab25", "recipientPartyId": "pty_019cd34e27bf78399b4e75b327d2cd36", "paymentRequestId": null, "transactionId": null, "amount": { "minorUnits": "150000", "currency": "usd" }, "status": "created", "description": "Invoice 2208", "tags": { "invoice_id": "2208" }, "failure": null, "approval": null, "cancellation": null, "submittedAt": null, "terminalAt": null, "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-15T14:30:00Z", "version": 1 } } } ``` A payment reached the public completed state. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2410", "object": "event", "type": "payment.completed", "resourceId": "pay_019cd3444a7a70efaf554fd8450d3403", "resourceType": "payment", "createdAt": "2026-01-15T14:30:05Z", "data": { "object": { "id": "pay_019cd3444a7a70efaf554fd8450d3403", "type": "payment", "senderPartyId": "pty_019cd34e27bf78399b4e75b327d2ab25", "recipientPartyId": "pty_019cd34e27bf78399b4e75b327d2cd36", "paymentRequestId": null, "transactionId": null, "amount": { "minorUnits": "150000", "currency": "usd" }, "status": "completed", "description": "Invoice 2208", "tags": { "invoice_id": "2208" }, "failure": null, "approval": null, "cancellation": null, "submittedAt": "2026-01-15T14:30:01Z", "terminalAt": "2026-01-15T14:30:05Z", "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-15T14:30:05Z", "version": 3 } } } ``` A payment failed during submit or money movement processing. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2411", "object": "event", "type": "payment.failed", "resourceId": "pay_019cd3444a7a70efaf554fd8450d3403", "resourceType": "payment", "createdAt": "2026-01-15T14:30:06Z", "data": { "object": { "id": "pay_019cd3444a7a70efaf554fd8450d3403", "type": "payment", "senderPartyId": "pty_019cd34e27bf78399b4e75b327d2ab25", "recipientPartyId": "pty_019cd34e27bf78399b4e75b327d2cd36", "paymentRequestId": null, "transactionId": null, "amount": { "minorUnits": "150000", "currency": "usd" }, "status": "failed", "description": "Invoice 2208", "tags": { "invoice_id": "2208" }, "failure": { "code": "INSUFFICIENT_FUNDS", "reason": "insufficient_funds" }, "approval": null, "cancellation": null, "submittedAt": "2026-01-15T14:30:01Z", "terminalAt": "2026-01-15T14:30:06Z", "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-15T14:30:06Z", "version": 3 } } } ``` A completed return-capable payment was returned. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2414", "object": "event", "type": "payment.returned", "resourceId": "pay_019cd3444a7a70efaf554fd8450d3403", "resourceType": "payment", "createdAt": "2026-01-16T14:30:06Z", "data": { "object": { "id": "pay_019cd3444a7a70efaf554fd8450d3403", "type": "payment", "senderPartyId": "pty_019cd34e27bf78399b4e75b327d2ab25", "recipientPartyId": "pty_019cd34e27bf78399b4e75b327d2cd36", "paymentRequestId": "prq_019cd3444a7a70efaf554fd8450d3403", "transactionId": null, "amount": { "minorUnits": "150000", "currency": "usd" }, "status": "returned", "description": "Invoice 2208", "tags": { "invoice_id": "2208" }, "failure": null, "approval": null, "cancellation": null, "submittedAt": "2026-01-15T14:30:01Z", "terminalAt": "2026-01-16T14:30:06Z", "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-16T14:30:06Z", "version": 4 } } } ``` A payment was canceled before money movement processing. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2412", "object": "event", "type": "payment.canceled", "resourceId": "pay_019cd3444a7a70efaf554fd8450d3404", "resourceType": "payment", "createdAt": "2026-01-15T14:30:04Z", "data": { "object": { "id": "pay_019cd3444a7a70efaf554fd8450d3404", "type": "payment", "senderPartyId": "pty_019cd34e27bf78399b4e75b327d2ab25", "recipientPartyId": "pty_019cd34e27bf78399b4e75b327d2cd36", "paymentRequestId": null, "transactionId": null, "amount": { "minorUnits": "150000", "currency": "usd" }, "status": "canceled", "description": "Invoice 2209", "tags": { "invoice_id": "2209" }, "failure": null, "approval": null, "cancellation": { "reason": "policy_canceled" }, "submittedAt": null, "terminalAt": "2026-01-15T14:30:04Z", "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-15T14:30:04Z", "version": 2 } } } ``` A payment approval was explicitly denied before money movement processing. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2413", "object": "event", "type": "payment.approval_denied", "resourceId": "pay_019cd3444a7a70efaf554fd8450d3405", "resourceType": "payment", "createdAt": "2026-01-15T14:30:04Z", "data": { "object": { "id": "pay_019cd3444a7a70efaf554fd8450d3405", "type": "payment", "senderPartyId": "pty_019cd34e27bf78399b4e75b327d2ab25", "recipientPartyId": "pty_019cd34e27bf78399b4e75b327d2cd36", "paymentRequestId": null, "transactionId": null, "amount": { "minorUnits": "150000", "currency": "usd" }, "status": "approval_denied", "description": "Invoice 2210", "tags": { "invoice_id": "2210" }, "failure": null, "approval": { "denialReason": "risk_policy_denied" }, "cancellation": null, "submittedAt": null, "terminalAt": "2026-01-15T14:30:04Z", "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-15T14:30:04Z", "version": 2 } } } ``` ## ACH An ACH transfer resource was created. ```json theme={null} { "id": "evt_00000000000000000000000000003001", "object": "event", "type": "ach.created", "resourceId": "ach_00000000000000000000000000000001", "resourceType": "ach", "createdAt": "2026-08-11T12:00:00Z", "data": { "object": { "id": "ach_00000000000000000000000000000001", "type": "ach", "direction": "debit", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalPartyId": "epty_00000000000000000000000000000001", "externalPartyAccountId": "epa_00000000000000000000000000000001", "mandateId": "mdt_00000000000000000000000000000001", "amount": { "minorUnits": "12500", "currency": "usd" }, "status": "created", "companyEntryDescription": "INVOICE", "secCode": "WEB", "submittedCompanyName": "Natural Test", "submittedCompanyEntryDescription": "INVOICE", "moneyMovementRequestId": null, "traceNumber": null, "failure": null, "return": null, "submittedAt": null, "settledAt": null, "expectedAvailableAt": null, "completedAt": null, "returnedAt": null, "terminalAt": null, "createdAt": "2026-08-11T12:00:00Z", "updatedAt": "2026-08-11T12:00:00Z", "version": 1 } } } ``` An ACH transfer was submitted to money movement processing. ```json theme={null} { "id": "evt_00000000000000000000000000003002", "object": "event", "type": "ach.processing", "resourceId": "ach_00000000000000000000000000000001", "resourceType": "ach", "createdAt": "2026-08-11T12:01:00Z", "data": { "object": { "id": "ach_00000000000000000000000000000001", "type": "ach", "direction": "debit", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalPartyId": "epty_00000000000000000000000000000001", "externalPartyAccountId": "epa_00000000000000000000000000000001", "mandateId": "mdt_00000000000000000000000000000001", "amount": { "minorUnits": "12500", "currency": "usd" }, "status": "processing", "companyEntryDescription": "INVOICE", "secCode": "WEB", "submittedCompanyName": "Natural Test", "submittedCompanyEntryDescription": "INVOICE", "moneyMovementRequestId": "mmr_00000000000000000000000000000001", "traceNumber": "123456780000001", "failure": null, "return": null, "submittedAt": "2026-08-11T12:01:00Z", "settledAt": null, "expectedAvailableAt": "2026-08-12T12:00:00Z", "completedAt": null, "returnedAt": null, "terminalAt": null, "createdAt": "2026-08-11T12:00:00Z", "updatedAt": "2026-08-11T12:01:00Z", "version": 2 } } } ``` An ACH transfer completed successfully. ```json theme={null} { "id": "evt_00000000000000000000000000003003", "object": "event", "type": "ach.completed", "resourceId": "ach_00000000000000000000000000000001", "resourceType": "ach", "createdAt": "2026-08-12T12:00:00Z", "data": { "object": { "id": "ach_00000000000000000000000000000001", "type": "ach", "direction": "debit", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalPartyId": "epty_00000000000000000000000000000001", "externalPartyAccountId": "epa_00000000000000000000000000000001", "mandateId": "mdt_00000000000000000000000000000001", "amount": { "minorUnits": "12500", "currency": "usd" }, "status": "completed", "companyEntryDescription": "INVOICE", "secCode": "WEB", "submittedCompanyName": "Natural Test", "submittedCompanyEntryDescription": "INVOICE", "moneyMovementRequestId": "mmr_00000000000000000000000000000001", "traceNumber": "123456780000001", "failure": null, "return": null, "submittedAt": "2026-08-11T12:01:00Z", "settledAt": "2026-08-12T12:00:00Z", "expectedAvailableAt": "2026-08-12T12:00:00Z", "completedAt": "2026-08-12T12:00:00Z", "returnedAt": null, "terminalAt": "2026-08-12T12:00:00Z", "createdAt": "2026-08-11T12:00:00Z", "updatedAt": "2026-08-12T12:00:00Z", "version": 3 } } } ``` An ACH transfer failed before completion. ```json theme={null} { "id": "evt_00000000000000000000000000003004", "object": "event", "type": "ach.failed", "resourceId": "ach_00000000000000000000000000000002", "resourceType": "ach", "createdAt": "2026-08-11T12:02:00Z", "data": { "object": { "id": "ach_00000000000000000000000000000002", "type": "ach", "direction": "credit", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalPartyId": "epty_00000000000000000000000000000001", "externalPartyAccountId": "epa_00000000000000000000000000000001", "mandateId": null, "amount": { "minorUnits": "12500", "currency": "usd" }, "status": "failed", "companyEntryDescription": "PAYROLL", "secCode": "PPD", "submittedCompanyName": "Natural Test", "submittedCompanyEntryDescription": "PAYROLL", "moneyMovementRequestId": "mmr_00000000000000000000000000000002", "traceNumber": null, "failure": { "code": "R03", "reason": "Bank account could not receive the credit." }, "return": null, "submittedAt": "2026-08-11T12:01:00Z", "settledAt": null, "expectedAvailableAt": null, "completedAt": null, "returnedAt": null, "terminalAt": "2026-08-11T12:02:00Z", "createdAt": "2026-08-11T12:00:00Z", "updatedAt": "2026-08-11T12:02:00Z", "version": 3 } } } ``` An ACH transfer was returned or reversed. ```json theme={null} { "id": "evt_00000000000000000000000000003005", "object": "event", "type": "ach.returned", "resourceId": "ach_00000000000000000000000000000001", "resourceType": "ach", "createdAt": "2026-08-13T12:00:00Z", "data": { "object": { "id": "ach_00000000000000000000000000000001", "type": "ach", "direction": "debit", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalPartyId": "epty_00000000000000000000000000000001", "externalPartyAccountId": "epa_00000000000000000000000000000001", "mandateId": "mdt_00000000000000000000000000000001", "amount": { "minorUnits": "12500", "currency": "usd" }, "status": "returned", "companyEntryDescription": "INVOICE", "secCode": "WEB", "submittedCompanyName": "Natural Test", "submittedCompanyEntryDescription": "INVOICE", "moneyMovementRequestId": "mmr_00000000000000000000000000000001", "traceNumber": "123456780000001", "failure": null, "return": { "code": "R01", "reason": "Insufficient funds", "dishonoredAt": null, "contestedAt": null, "fundsUnlockedAt": "2026-08-13T12:05:00Z" }, "submittedAt": "2026-08-11T12:01:00Z", "settledAt": "2026-08-12T12:00:00Z", "expectedAvailableAt": null, "completedAt": "2026-08-12T12:00:00Z", "returnedAt": "2026-08-13T12:00:00Z", "terminalAt": "2026-08-13T12:00:00Z", "createdAt": "2026-08-11T12:00:00Z", "updatedAt": "2026-08-13T12:00:00Z", "version": 4 } } } ``` ## Mandates A debit authorization mandate was created. ```json theme={null} { "id": "evt_00000000000000000000000000003101", "object": "event", "type": "mandate.created", "resourceId": "mdt_00000000000000000000000000000001", "resourceType": "mandate", "createdAt": "2026-08-11T12:00:00Z", "data": { "object": { "id": "mdt_00000000000000000000000000000001", "type": "mandate", "externalPartyAccountId": "epa_00000000000000000000000000000001", "originatorPartyId": "pty_00000000000000000000000000000001", "scheme": "ach_web", "amountType": "variable", "fixedAmount": null, "maximumAmount": { "minorUnits": "50000", "currency": "usd" }, "currency": "usd", "frequency": "recurring", "effectiveAt": "2026-08-11T12:00:00Z", "expiresAt": null, "status": "active", "revokedAt": null, "revocationReason": null, "supersedesMandateId": null, "supersededByMandateId": null, "consumedAt": null, "terminatedAt": null, "retainUntil": null, "evidence": { "captureMethod": "web", "capturedAt": "2026-08-11T12:00:00Z", "ipAddress": "203.0.113.10", "authorizationText": null, "authorizationUri": "https://example.com/mandates/mdt_00000000000000000000000000000001/authorization" }, "createdAt": "2026-08-11T12:00:00Z", "updatedAt": "2026-08-11T12:00:00Z", "version": 1 } } } ``` A debit authorization mandate was revoked. ```json theme={null} { "id": "evt_00000000000000000000000000003102", "object": "event", "type": "mandate.revoked", "resourceId": "mdt_00000000000000000000000000000001", "resourceType": "mandate", "createdAt": "2026-08-12T12:00:00Z", "data": { "object": { "id": "mdt_00000000000000000000000000000001", "type": "mandate", "externalPartyAccountId": "epa_00000000000000000000000000000001", "originatorPartyId": "pty_00000000000000000000000000000001", "scheme": "ach_web", "amountType": "variable", "fixedAmount": null, "maximumAmount": { "minorUnits": "50000", "currency": "usd" }, "currency": "usd", "frequency": "recurring", "effectiveAt": "2026-08-11T12:00:00Z", "expiresAt": null, "status": "revoked", "revokedAt": "2026-08-12T12:00:00Z", "revocationReason": "Customer revoked authorization.", "supersedesMandateId": null, "supersededByMandateId": null, "consumedAt": null, "terminatedAt": "2026-08-12T12:00:00Z", "retainUntil": "2028-08-12T12:00:00Z", "evidence": { "captureMethod": "web", "capturedAt": "2026-08-11T12:00:00Z", "ipAddress": "203.0.113.10", "authorizationText": null, "authorizationUri": "https://example.com/mandates/mdt_00000000000000000000000000000001/authorization" }, "createdAt": "2026-08-11T12:00:00Z", "updatedAt": "2026-08-12T12:00:00Z", "version": 2 } } } ``` ## Deposits The public deposit transfer resource became visible. API-initiated deposits usually emit with status "created" before money movement submission; deposits discovered from money movement may emit with the current status, such as "processing". ```json theme={null} { "id": "evt_00000000000000000000000000001001", "object": "event", "type": "deposit.created", "resourceId": "trf_00000000000000000000000000000001", "resourceType": "transfer", "createdAt": "2026-06-13T12:00:00Z", "data": { "object": { "id": "trf_00000000000000000000000000000001", "type": "deposit", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalAccountId": "eac_00000000000000000000000000000001", "transactionId": null, "amount": { "minorUnits": "2500", "currency": "usd" }, "status": "created", "description": "Deposit", "tags": { "workflow": "treasury" }, "externalAccountDisplayMask": null, "expectedAvailableAt": null, "failure": null, "approval": null, "cancellation": null, "return": null, "submittedAt": null, "settledAt": null, "returnedAt": null, "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-13T12:00:00Z", "version": 1 } } } ``` A deposit reached the public completed state. ```json theme={null} { "id": "evt_00000000000000000000000000001002", "object": "event", "type": "deposit.completed", "resourceId": "trf_00000000000000000000000000000001", "resourceType": "transfer", "createdAt": "2026-06-14T09:30:00Z", "data": { "object": { "id": "trf_00000000000000000000000000000001", "type": "deposit", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalAccountId": "eac_00000000000000000000000000000001", "transactionId": "txn_00000000000000000000000000000001", "amount": { "minorUnits": "2500", "currency": "usd" }, "status": "completed", "description": "Deposit", "tags": { "workflow": "treasury" }, "externalAccountDisplayMask": "1234", "expectedAvailableAt": "2026-06-14T09:30:00Z", "failure": null, "approval": null, "cancellation": null, "return": null, "submittedAt": "2026-06-13T12:01:00Z", "settledAt": "2026-06-14T09:30:00Z", "returnedAt": null, "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-14T09:30:00Z", "version": 4 } } } ``` A deposit failed during submit or money movement processing. ```json theme={null} { "id": "evt_00000000000000000000000000001003", "object": "event", "type": "deposit.failed", "resourceId": "trf_00000000000000000000000000000002", "resourceType": "transfer", "createdAt": "2026-06-13T12:05:00Z", "data": { "object": { "id": "trf_00000000000000000000000000000002", "type": "deposit", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalAccountId": "eac_00000000000000000000000000000002", "transactionId": "txn_00000000000000000000000000000002", "amount": { "minorUnits": "5000", "currency": "usd" }, "status": "failed", "description": null, "tags": { "workflow": "treasury" }, "externalAccountDisplayMask": "6789", "expectedAvailableAt": null, "failure": { "code": "INSUFFICIENT_FUNDS", "reason": "insufficient_funds" }, "approval": null, "cancellation": null, "return": null, "submittedAt": "2026-06-13T12:01:00Z", "settledAt": null, "returnedAt": null, "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-13T12:05:00Z", "version": 3 } } } ``` An incoming deposit was returned by the bank. ```json theme={null} { "id": "evt_00000000000000000000000000001004", "object": "event", "type": "deposit.returned", "resourceId": "trf_00000000000000000000000000000003", "resourceType": "transfer", "createdAt": "2026-06-15T16:20:00Z", "data": { "object": { "id": "trf_00000000000000000000000000000003", "type": "deposit", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalAccountId": "eac_00000000000000000000000000000003", "transactionId": "txn_00000000000000000000000000000003", "amount": { "minorUnits": "2500", "currency": "usd" }, "status": "returned", "description": "Deposit", "tags": { "workflow": "treasury" }, "externalAccountDisplayMask": "1234", "expectedAvailableAt": "2026-06-14T09:30:00Z", "failure": null, "approval": null, "cancellation": null, "return": { "code": "R01", "reason": "Insufficient Funds" }, "submittedAt": "2026-06-13T12:01:00Z", "settledAt": "2026-06-14T09:30:00Z", "returnedAt": "2026-06-15T16:20:00Z", "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-15T16:20:00Z", "version": 6 } } } ``` A deposit was canceled before money movement processing. ```json theme={null} { "id": "evt_00000000000000000000000000001005", "object": "event", "type": "deposit.canceled", "resourceId": "trf_00000000000000000000000000000004", "resourceType": "transfer", "createdAt": "2026-06-13T12:03:00Z", "data": { "object": { "id": "trf_00000000000000000000000000000004", "type": "deposit", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalAccountId": "eac_00000000000000000000000000000004", "transactionId": null, "amount": { "minorUnits": "2500", "currency": "usd" }, "status": "canceled", "description": null, "tags": { "workflow": "treasury" }, "externalAccountDisplayMask": null, "expectedAvailableAt": null, "failure": null, "approval": null, "cancellation": { "reason": "policy_canceled" }, "return": null, "submittedAt": null, "settledAt": null, "returnedAt": null, "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-13T12:03:00Z", "version": 2 } } } ``` A deposit approval was explicitly denied before money movement processing. ```json theme={null} { "id": "evt_00000000000000000000000000001006", "object": "event", "type": "deposit.approval_denied", "resourceId": "trf_00000000000000000000000000000005", "resourceType": "transfer", "createdAt": "2026-06-13T12:04:00Z", "data": { "object": { "id": "trf_00000000000000000000000000000005", "type": "deposit", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalAccountId": "eac_00000000000000000000000000000005", "transactionId": null, "amount": { "minorUnits": "2500", "currency": "usd" }, "status": "approval_denied", "description": null, "tags": { "workflow": "treasury" }, "externalAccountDisplayMask": null, "expectedAvailableAt": null, "failure": null, "approval": { "denialReason": "risk_policy_denied" }, "cancellation": null, "return": null, "submittedAt": null, "settledAt": null, "returnedAt": null, "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-13T12:04:00Z", "version": 2 } } } ``` ## Withdrawals A withdrawal was created and funds are being moved out of a wallet. ```json theme={null} { "id": "evt_00000000000000000000000000002001", "object": "event", "type": "withdrawal.created", "resourceId": "trf_00000000000000000000000000001001", "resourceType": "transfer", "createdAt": "2026-06-13T12:00:00Z", "data": { "object": { "id": "trf_00000000000000000000000000001001", "type": "withdrawal", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalAccountId": "eac_00000000000000000000000000001001", "transactionId": null, "amount": { "minorUnits": "2500", "currency": "usd" }, "status": "created", "description": "Withdrawal", "tags": { "workflow": "treasury" }, "externalAccountDisplayMask": null, "expectedAvailableAt": null, "failure": null, "approval": null, "cancellation": null, "return": null, "submittedAt": null, "settledAt": null, "returnedAt": null, "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-13T12:00:00Z", "version": 1 } } } ``` A withdrawal completed successfully. ```json theme={null} { "id": "evt_00000000000000000000000000002002", "object": "event", "type": "withdrawal.completed", "resourceId": "trf_00000000000000000000000000001001", "resourceType": "transfer", "createdAt": "2026-06-14T09:30:00Z", "data": { "object": { "id": "trf_00000000000000000000000000001001", "type": "withdrawal", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalAccountId": "eac_00000000000000000000000000001001", "transactionId": "txn_00000000000000000000000000001001", "amount": { "minorUnits": "2500", "currency": "usd" }, "status": "completed", "description": "Withdrawal", "tags": { "workflow": "treasury" }, "externalAccountDisplayMask": "1234", "expectedAvailableAt": null, "failure": null, "approval": null, "cancellation": null, "return": null, "submittedAt": "2026-06-13T12:01:00Z", "settledAt": "2026-06-14T09:30:00Z", "returnedAt": null, "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-14T09:30:00Z", "version": 4 } } } ``` A withdrawal failed before settling. ```json theme={null} { "id": "evt_00000000000000000000000000002003", "object": "event", "type": "withdrawal.failed", "resourceId": "trf_00000000000000000000000000001002", "resourceType": "transfer", "createdAt": "2026-06-13T12:05:00Z", "data": { "object": { "id": "trf_00000000000000000000000000001002", "type": "withdrawal", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalAccountId": "eac_00000000000000000000000000001002", "transactionId": "txn_00000000000000000000000000001002", "amount": { "minorUnits": "5000", "currency": "usd" }, "status": "failed", "description": null, "tags": { "workflow": "treasury" }, "externalAccountDisplayMask": "6789", "expectedAvailableAt": null, "failure": { "code": "INSUFFICIENT_FUNDS", "reason": "insufficient_funds" }, "approval": null, "cancellation": null, "return": null, "submittedAt": "2026-06-13T12:01:00Z", "settledAt": null, "returnedAt": null, "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-13T12:05:00Z", "version": 3 } } } ``` A settled withdrawal was returned or reversed. ```json theme={null} { "id": "evt_00000000000000000000000000002004", "object": "event", "type": "withdrawal.returned", "resourceId": "trf_00000000000000000000000000001003", "resourceType": "transfer", "createdAt": "2026-06-15T16:20:00Z", "data": { "object": { "id": "trf_00000000000000000000000000001003", "type": "withdrawal", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalAccountId": "eac_00000000000000000000000000001003", "transactionId": "txn_00000000000000000000000000001003", "amount": { "minorUnits": "2500", "currency": "usd" }, "status": "returned", "description": "Withdrawal", "tags": { "workflow": "treasury" }, "externalAccountDisplayMask": "1234", "expectedAvailableAt": null, "failure": null, "approval": null, "cancellation": null, "return": { "code": "R03", "reason": "No account/unable to locate account" }, "submittedAt": "2026-06-13T12:01:00Z", "settledAt": "2026-06-14T09:30:00Z", "returnedAt": "2026-06-15T16:20:00Z", "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-15T16:20:00Z", "version": 6 } } } ``` A withdrawal was canceled before money movement processing. ```json theme={null} { "id": "evt_00000000000000000000000000002005", "object": "event", "type": "withdrawal.canceled", "resourceId": "trf_00000000000000000000000000001004", "resourceType": "transfer", "createdAt": "2026-06-13T12:03:00Z", "data": { "object": { "id": "trf_00000000000000000000000000001004", "type": "withdrawal", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalAccountId": "eac_00000000000000000000000000001004", "transactionId": null, "amount": { "minorUnits": "2500", "currency": "usd" }, "status": "canceled", "description": null, "tags": { "workflow": "treasury" }, "externalAccountDisplayMask": null, "expectedAvailableAt": null, "failure": null, "approval": null, "cancellation": { "reason": "policy_canceled" }, "return": null, "submittedAt": null, "settledAt": null, "returnedAt": null, "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-13T12:03:00Z", "version": 2 } } } ``` A withdrawal approval was explicitly denied before money movement processing. ```json theme={null} { "id": "evt_00000000000000000000000000002006", "object": "event", "type": "withdrawal.approval_denied", "resourceId": "trf_00000000000000000000000000001005", "resourceType": "transfer", "createdAt": "2026-06-13T12:04:00Z", "data": { "object": { "id": "trf_00000000000000000000000000001005", "type": "withdrawal", "partyId": "pty_00000000000000000000000000000001", "walletId": "wal_00000000000000000000000000000001", "externalAccountId": "eac_00000000000000000000000000001005", "transactionId": null, "amount": { "minorUnits": "2500", "currency": "usd" }, "status": "approval_denied", "description": null, "tags": { "workflow": "treasury" }, "externalAccountDisplayMask": null, "expectedAvailableAt": null, "failure": null, "approval": { "denialReason": "risk_policy_denied" }, "cancellation": null, "return": null, "submittedAt": null, "settledAt": null, "returnedAt": null, "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-13T12:04:00Z", "version": 2 } } } ``` ## Payment requests A merchant created a payment request. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2412", "object": "event", "type": "payment_request.created", "resourceId": "prq_019cd3444a7a70efaf554fd8450d3404", "resourceType": "payment_request", "createdAt": "2026-01-15T14:30:00Z", "data": { "object": { "id": "prq_019cd3444a7a70efaf554fd8450d3404", "type": "payment_request", "payeePartyId": "pty_019cd3444a7a70efaf554fd8450d1111", "payerPartyId": null, "payerAgentId": null, "amount": { "minorUnits": "300000", "currency": "usd" }, "status": "open", "description": "Invoice 9930", "tags": { "invoice_id": "9930" }, "linkedPaymentId": null, "completedAt": null, "canceledAt": null, "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-15T14:30:00Z", "version": 1 } } } ``` The linked payment for a payment request completed successfully. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2413", "object": "event", "type": "payment_request.completed", "resourceId": "prq_019cd3444a7a70efaf554fd8450d3404", "resourceType": "payment_request", "createdAt": "2026-01-15T18:30:00Z", "data": { "object": { "id": "prq_019cd3444a7a70efaf554fd8450d3404", "type": "payment_request", "payeePartyId": "pty_019cd3444a7a70efaf554fd8450d1111", "payerPartyId": "pty_019cd3444a7a70efaf554fd8450d2222", "payerAgentId": null, "amount": { "minorUnits": "300000", "currency": "usd" }, "status": "completed", "description": "Invoice 9930", "tags": { "invoice_id": "9930" }, "linkedPaymentId": "pay_019cd3444a7a70efaf554fd8450d5555", "completedAt": "2026-01-15T18:30:00Z", "canceledAt": null, "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-15T18:30:00Z", "version": 7 } } } ``` A merchant canceled an open payment request. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2414", "object": "event", "type": "payment_request.canceled", "resourceId": "prq_019cd3444a7a70efaf554fd8450d3404", "resourceType": "payment_request", "createdAt": "2026-01-15T14:45:00Z", "data": { "object": { "id": "prq_019cd3444a7a70efaf554fd8450d3404", "type": "payment_request", "payeePartyId": "pty_019cd3444a7a70efaf554fd8450d1111", "payerPartyId": "pty_019cd3444a7a70efaf554fd8450d2222", "payerAgentId": null, "amount": { "minorUnits": "300000", "currency": "usd" }, "status": "canceled", "description": "Invoice 9930", "tags": { "invoice_id": "9930" }, "linkedPaymentId": null, "completedAt": null, "canceledAt": "2026-01-15T14:45:00Z", "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-15T14:45:00Z", "version": 3 } } } ``` The payer declined an open payment request. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2415", "object": "event", "type": "payment_request.declined", "resourceId": "prq_019cd3444a7a70efaf554fd8450d3404", "resourceType": "payment_request", "createdAt": "2026-01-15T15:00:00Z", "data": { "object": { "id": "prq_019cd3444a7a70efaf554fd8450d3404", "type": "payment_request", "payeePartyId": "pty_019cd3444a7a70efaf554fd8450d1111", "payerPartyId": "pty_019cd3444a7a70efaf554fd8450d2222", "payerAgentId": null, "amount": { "minorUnits": "300000", "currency": "usd" }, "status": "declined", "description": "Invoice 9930", "tags": { "invoice_id": "9930" }, "linkedPaymentId": null, "completedAt": null, "canceledAt": null, "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-15T15:00:00Z", "version": 4 } } } ``` A payment request was created naming this party as the payer. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2500", "object": "event", "type": "payment_request.incoming", "resourceId": "prq_019cd3444a7a70efaf554fd8450d3500", "resourceType": "payment_request", "createdAt": "2026-01-15T14:30:00Z", "data": { "object": { "id": "prq_019cd3444a7a70efaf554fd8450d3500", "type": "payment_request", "payeePartyId": "pty_019cd34e27bf78399b4e75b327d2ab25", "payerPartyId": "pty_019cd3444a7a70efaf554fd8450d3501", "payerAgentId": null, "amount": { "minorUnits": "500", "currency": "USD" }, "status": "OPEN", "description": "Invoice 7", "tags": { "invoice_id": "7" }, "linkedPaymentId": null, "completedAt": null, "canceledAt": null, "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-15T14:30:00Z", "version": 1 } } } ``` ## Approvals A customer approval is required before the payment can continue. ```json theme={null} { "id": "evt_00000000000000000000000000007001", "object": "event", "type": "approval.required", "resourceId": "apr_00000000000000000000000000000001", "resourceType": "approval", "createdAt": "2026-06-13T12:00:00Z", "data": { "object": { "id": "apr_00000000000000000000000000000001", "type": "approval", "status": "pending", "approverPartyId": "pty_00000000000000000000000000000003", "target": { "type": "payment", "id": "pay_00000000000000000000000000000001" }, "reasons": [ { "type": "limitExceeded", "limitType": "dailyAmount", "limit": { "minorUnits": "10000", "currency": "usd" }, "actual": { "minorUnits": "15000", "currency": "usd" } } ], "denialNote": null, "cancellationReason": null, "resolvedByPartyId": null, "resolvedAt": null, "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-13T12:00:00Z", "version": 1 } } } ``` A customer approval was approved. ```json theme={null} { "id": "evt_00000000000000000000000000007002", "object": "event", "type": "approval.approved", "resourceId": "apr_00000000000000000000000000000002", "resourceType": "approval", "createdAt": "2026-06-13T12:05:00Z", "data": { "object": { "id": "apr_00000000000000000000000000000002", "type": "approval", "status": "approved", "approverPartyId": "pty_00000000000000000000000000000003", "target": { "type": "payment_request", "id": "prq_00000000000000000000000000000001" }, "reasons": [ { "type": "limitExceeded", "limitType": "perTransactionAmount", "limit": { "minorUnits": "10000", "currency": "usd" }, "actual": { "minorUnits": "15000", "currency": "usd" } } ], "denialNote": null, "cancellationReason": null, "resolvedByPartyId": "pty_00000000000000000000000000000003", "resolvedAt": "2026-06-13T12:05:00Z", "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-13T12:05:00Z", "version": 2 } } } ``` A customer approval was denied. ```json theme={null} { "id": "evt_00000000000000000000000000007003", "object": "event", "type": "approval.denied", "resourceId": "apr_00000000000000000000000000000003", "resourceType": "approval", "createdAt": "2026-06-13T12:07:00Z", "data": { "object": { "id": "apr_00000000000000000000000000000003", "type": "approval", "status": "denied", "approverPartyId": "pty_00000000000000000000000000000003", "target": { "type": "payment", "id": "pay_00000000000000000000000000000003" }, "reasons": [ { "type": "limitExceeded", "limitType": "monthlyAmount", "limit": { "minorUnits": "100000", "currency": "usd" }, "actual": { "minorUnits": "125000", "currency": "usd" } } ], "denialNote": "Over the delegated limit", "cancellationReason": null, "resolvedByPartyId": "pty_00000000000000000000000000000003", "resolvedAt": "2026-06-13T12:07:00Z", "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-13T12:07:00Z", "version": 2 } } } ``` A customer approval was canceled before completion. ```json theme={null} { "id": "evt_00000000000000000000000000007004", "object": "event", "type": "approval.canceled", "resourceId": "apr_00000000000000000000000000000004", "resourceType": "approval", "createdAt": "2026-06-13T12:08:00Z", "data": { "object": { "id": "apr_00000000000000000000000000000004", "type": "approval", "status": "canceled", "approverPartyId": "pty_00000000000000000000000000000003", "target": { "type": "payment", "id": "pay_00000000000000000000000000000004" }, "reasons": [], "denialNote": null, "cancellationReason": "NON_EXECUTABLE", "resolvedByPartyId": null, "resolvedAt": "2026-06-13T12:08:00Z", "createdAt": "2026-06-13T12:00:00Z", "updatedAt": "2026-06-13T12:08:00Z", "version": 3 } } } ``` ## Parties A party's information was updated. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d221b", "object": "event", "type": "party.updated", "resourceId": "pty_019cd34e27bf78399b4e75b327d2ab25", "resourceType": "party", "createdAt": "2026-01-15T14:30:00Z", "data": { "object": { "type": "org", "legalName": "Acme Corp", "displayName": "Acme", "status": "active", "email": "admin@acme.com", "createdAt": "2026-01-10T10:00:00Z", "updatedAt": "2026-01-15T14:30:00Z", "version": 3 } } } ``` ## Compliance A party's canonical compliance case reached a public verification outcome. ```json theme={null} { "id": "evt_00000000000000000000000000000040", "object": "event", "type": "compliance_case.updated", "resourceId": "cpc_00000000000000000000000000000001", "resourceType": "compliance_case", "createdAt": "2026-01-15T14:30:00Z", "data": { "object": { "id": "cpc_00000000000000000000000000000001", "type": "compliance_case", "attributes": { "partyId": "pty_00000000000000000000000000000001", "status": "approved" }, "version": 7 } } } ``` ## Wallets A wallet was created for a party. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d221a", "object": "event", "type": "wallet.created", "resourceId": "wal_019cd3444a7a70efaf554fd8450d334b", "resourceType": "wallet", "createdAt": "2026-01-15T14:30:00Z", "data": { "object": { "partyId": "pty_019cd34e27bf78399b4e75b327d2ab25", "walletType": "standard", "status": "active", "displayName": "My Wallet", "tags": { "environment": "production" }, "currency": "usd", "freezeDetails": null, "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-15T14:30:00Z", "createdBy": "usr_550e8400e29b41d4a716446655440000", "version": 1 } } } ``` ## Delegations A developer created a per-agent invitation for a customer to accept. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2301", "object": "event", "type": "agent_delegation_invitation.created", "resourceId": "adi_019cd3444a7a70efaf554fd8450d4501", "resourceType": "agent_delegation_invitation", "createdAt": "2026-01-15T14:30:00Z", "data": { "object": { "invitationId": "adi_019cd3444a7a70efaf554fd8450d4501", "agentId": "agt_019cd3444a7a70efaf554fd8450d4502", "delegateePartyId": "pty_019cd34e27bf78399b4e75b327d2cd36", "delegatorPartyId": null, "customerEmail": "customer@example.com", "status": "pending", "permissions": [ "payments.read", "payments.create" ], "limits": { "perTransactionCents": 50000 }, "expiresAt": "2026-02-15T14:30:00Z", "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-15T14:30:00Z", "version": 1, "tags": { "campaign": "q3_reactivation", "crm_id": "hub_84921" } } } } ``` A customer accepted a per-agent invitation; the per-agent grant is now active. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2302", "object": "event", "type": "agent_delegation_invitation.accepted", "resourceId": "adi_019cd3444a7a70efaf554fd8450d4501", "resourceType": "agent_delegation_invitation", "createdAt": "2026-01-15T15:00:00Z", "data": { "object": { "invitationId": "adi_019cd3444a7a70efaf554fd8450d4501", "agentId": "agt_019cd3444a7a70efaf554fd8450d4502", "delegateePartyId": "pty_019cd34e27bf78399b4e75b327d2cd36", "delegatorPartyId": "pty_019cd34e27bf78399b4e75b327d2ab25", "customerEmail": "customer@example.com", "status": "accepted", "permissions": [ "payments.read", "payments.create" ], "limits": { "perTransactionCents": 50000 }, "expiresAt": "2026-02-15T14:30:00Z", "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-15T15:00:00Z", "version": 2, "tags": { "campaign": "q3_reactivation", "crm_id": "hub_84921" } } } } ``` A customer declined a per-agent invitation. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2303", "object": "event", "type": "agent_delegation_invitation.declined", "resourceId": "adi_019cd3444a7a70efaf554fd8450d4501", "resourceType": "agent_delegation_invitation", "createdAt": "2026-01-15T15:00:00Z", "data": { "object": { "invitationId": "adi_019cd3444a7a70efaf554fd8450d4501", "agentId": "agt_019cd3444a7a70efaf554fd8450d4502", "delegateePartyId": "pty_019cd34e27bf78399b4e75b327d2cd36", "delegatorPartyId": null, "customerEmail": "customer@example.com", "status": "declined", "permissions": [ "payments.read", "payments.create" ], "limits": { "perTransactionCents": 50000 }, "expiresAt": "2026-02-15T14:30:00Z", "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-15T15:00:00Z", "version": 2, "tags": { "campaign": "q3_reactivation", "crm_id": "hub_84921" } } } } ``` A pending per-agent invitation was canceled (e.g. by agent or developer retire). ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2304", "object": "event", "type": "agent_delegation_invitation.canceled", "resourceId": "adi_019cd3444a7a70efaf554fd8450d4501", "resourceType": "agent_delegation_invitation", "createdAt": "2026-01-15T15:00:00Z", "data": { "object": { "invitationId": "adi_019cd3444a7a70efaf554fd8450d4501", "agentId": "agt_019cd3444a7a70efaf554fd8450d4502", "delegateePartyId": "pty_019cd34e27bf78399b4e75b327d2cd36", "delegatorPartyId": null, "customerEmail": "customer@example.com", "status": "canceled", "permissions": [ "payments.read", "payments.create" ], "limits": { "perTransactionCents": 50000 }, "expiresAt": "2026-02-15T14:30:00Z", "cancelReason": "developer_retired", "createdAt": "2026-01-15T14:30:00Z", "updatedAt": "2026-01-15T15:00:00Z", "version": 2, "tags": { "campaign": "q3_reactivation", "crm_id": "hub_84921" } } } } ``` A per-agent grant was revoked. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2305", "object": "event", "type": "agent_delegation.revoked", "resourceId": "adl_019cd3444a7a70efaf554fd8450d4503", "resourceType": "agent_delegation", "createdAt": "2026-01-15T16:00:00Z", "data": { "object": { "agentDelegationId": "adl_019cd3444a7a70efaf554fd8450d4503", "delegationId": "dlg_019cd3444a7a70efaf554fd8450d4504", "agentId": "agt_019cd3444a7a70efaf554fd8450d4502", "permissions": [ "payments.read", "payments.create" ], "status": "revoked", "revokeReason": "customer", "createdAt": "2026-01-15T15:00:00Z", "updatedAt": "2026-01-15T16:00:00Z", "version": 2 } } } ``` A parent delegation row went active. ```json theme={null} { "id": "evt_00000000000000000000000000000041", "object": "event", "type": "delegation.activated", "resourceId": "dlg_00000000000000000000000000000001", "resourceType": "delegation", "createdAt": "2026-01-15T14:30:00Z", "data": { "object": { "delegatorPartyId": "pty_00000000000000000000000000000001", "delegateePartyId": "pty_00000000000000000000000000000002", "permissions": [ "party.read", "payments.read" ], "status": "active", "sourceType": "link", "sourceId": "ivl_00000000000000000000000000000001", "createdAt": "2026-01-10T10:00:00Z", "version": 2 } } } ``` A parent delegation row was revoked (last-active-agent cascade). ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d2307", "object": "event", "type": "delegation.revoked", "resourceId": "dlg_019cd3444a7a70efaf554fd8450d4504", "resourceType": "delegation", "createdAt": "2026-01-15T16:00:00Z", "data": { "object": { "delegatorPartyId": "pty_019cd34e27bf78399b4e75b327d2ab25", "delegateePartyId": "pty_019cd34e27bf78399b4e75b327d2cd36", "permissions": [ "payments.read", "payments.create" ], "status": "revoked", "sourceType": "invitation", "sourceId": "adi_019cd3444a7a70efaf554fd8450d6812", "createdAt": "2026-01-15T15:00:00Z", "version": 2 } } } ``` ## Invitation links A reusable invitation link was created with its initial offered agents. ```json theme={null} { "id": "evt_00000000000000000000000000000051", "object": "event", "type": "invitation_link.created", "resourceId": "ivl_00000000000000000000000000000001", "resourceType": "invitation_link", "createdAt": "2026-02-01T09:00:00Z", "data": { "object": { "id": "ivl_00000000000000000000000000000001", "name": "Vendor onboarding link", "status": "active", "delegateePartyId": "pty_00000000000000000000000000000002", "permissions": [ "payments.read", "payments.create" ], "offeredAgents": [ { "agentId": "agt_00000000000000000000000000000011", "permissions": [ "payments.read", "payments.create" ], "limits": { "perTransactionCents": 50000, "perDayCents": 200000 } }, { "agentId": "agt_00000000000000000000000000000012", "permissions": [ "payments.read" ] } ], "expiresAt": "2026-03-01T00:00:00Z", "tags": { "campaign": "vendor_onboarding" }, "createdAt": "2026-02-01T09:00:00Z", "version": 1 } } } ``` An invitation link was revoked and its open offers were withdrawn. ```json theme={null} { "id": "evt_00000000000000000000000000000052", "object": "event", "type": "invitation_link.revoked", "resourceId": "ivl_00000000000000000000000000000001", "resourceType": "invitation_link", "createdAt": "2026-02-10T16:45:00Z", "data": { "object": { "id": "ivl_00000000000000000000000000000001", "name": "Vendor onboarding link", "status": "revoked", "delegateePartyId": "pty_00000000000000000000000000000002", "permissions": [ "payments.read", "payments.create" ], "offeredAgents": [ { "agentId": "agt_00000000000000000000000000000011", "permissions": [ "payments.read", "payments.create" ], "limits": { "perTransactionCents": 50000, "perDayCents": 200000 } }, { "agentId": "agt_00000000000000000000000000000012", "permissions": [ "payments.read" ] } ], "tags": { "campaign": "vendor_onboarding" }, "createdAt": "2026-02-01T09:00:00Z", "version": 2 } } } ``` An agent offer was added to an invitation link. ```json theme={null} { "id": "evt_00000000000000000000000000000053", "object": "event", "type": "invitation_link.agent_added", "resourceId": "ivl_00000000000000000000000000000001", "resourceType": "invitation_link", "createdAt": "2026-02-03T11:20:00Z", "data": { "object": { "id": "ivl_00000000000000000000000000000001", "name": "Vendor onboarding link", "status": "active", "delegateePartyId": "pty_00000000000000000000000000000002", "permissions": [ "payments.read", "payments.create" ], "offeredAgents": [ { "agentId": "agt_00000000000000000000000000000011", "permissions": [ "payments.read", "payments.create" ], "limits": { "perTransactionCents": 50000, "perDayCents": 200000 } }, { "agentId": "agt_00000000000000000000000000000012", "permissions": [ "payments.read" ] } ], "expiresAt": "2026-03-01T00:00:00Z", "tags": { "campaign": "vendor_onboarding" }, "createdAt": "2026-02-01T09:00:00Z", "version": 1 } } } ``` An agent offer was removed from an invitation link. ```json theme={null} { "id": "evt_00000000000000000000000000000054", "object": "event", "type": "invitation_link.agent_removed", "resourceId": "ivl_00000000000000000000000000000001", "resourceType": "invitation_link", "createdAt": "2026-02-04T08:05:00Z", "data": { "object": { "id": "ivl_00000000000000000000000000000001", "name": "Vendor onboarding link", "status": "active", "delegateePartyId": "pty_00000000000000000000000000000002", "permissions": [ "payments.read", "payments.create" ], "offeredAgents": [ { "agentId": "agt_00000000000000000000000000000012", "permissions": [ "payments.read" ] } ], "expiresAt": "2026-03-01T00:00:00Z", "tags": { "campaign": "vendor_onboarding" }, "createdAt": "2026-02-01T09:00:00Z", "version": 1 } } } ``` A customer redeemed an invitation link; the resulting access grant is reported by its own events. ```json theme={null} { "id": "evt_00000000000000000000000000000055", "object": "event", "type": "invitation_link.redeemed", "resourceId": "ivl_00000000000000000000000000000001", "resourceType": "invitation_link", "createdAt": "2026-02-05T19:30:00Z", "data": { "object": { "id": "ivl_00000000000000000000000000000001", "name": "Vendor onboarding link", "status": "active", "delegateePartyId": "pty_00000000000000000000000000000002", "permissions": [ "payments.read", "payments.create" ], "offeredAgents": [ { "agentId": "agt_00000000000000000000000000000011", "permissions": [ "payments.read", "payments.create" ], "limits": { "perTransactionCents": 50000, "perDayCents": 200000 } }, { "agentId": "agt_00000000000000000000000000000012", "permissions": [ "payments.read" ] } ], "expiresAt": "2026-03-01T00:00:00Z", "tags": { "campaign": "vendor_onboarding" }, "createdAt": "2026-02-01T09:00:00Z", "version": 1 } } } ``` ## External party accounts An external party bank account passed validation. ```json theme={null} { "id": "evt_00000000000000000000000000003201", "object": "event", "type": "external_party_account.validated", "resourceId": "epa_00000000000000000000000000000001", "resourceType": "external_party_account", "createdAt": "2026-08-11T12:00:00Z", "data": { "object": { "id": "epa_00000000000000000000000000000001", "type": "external_party_account", "externalPartyId": "epty_00000000000000000000000000000001", "moneyMovementExternalAccountId": "eac_00000000000000000000000000000001", "accountType": "checking", "last4": "6789", "mask": "****6789", "validationState": "validated", "validationMethod": "plaid_database_auth", "plaidVerificationStatus": "database_insights_pass", "validationRiskFlag": false, "createdAt": "2026-08-11T12:00:00Z", "updatedAt": "2026-08-11T12:00:00Z", "version": 1 } } } ``` ## External accounts An external bank account was linked. ```json theme={null} { "id": "evt_019cd3444a7a70efaf554fd8450d221d", "object": "event", "type": "external_account.connected", "resourceId": "eac_019cd3444a7a70efaf554fd8450d556e", "resourceType": "external_account", "createdAt": "2026-01-15T14:30:00Z", "data": { "object": { "version": 1, "tags": { "environment": "production" } } } } ``` # Get event Source: https://docs.natural.com/api-reference/events/get-event /api-reference/openapi.json get /events/{eventId} Get an event # List events Source: https://docs.natural.com/api-reference/events/list-events /api-reference/openapi.json get /events List events # Redeliver event Source: https://docs.natural.com/api-reference/events/redeliver-event /api-reference/openapi.json post /webhooks/{webhookId}/events/{eventId}/redeliver Send the stored event once to the same webhook using its current URL and signing secret. Automatic retries continue independently, and delegated events require the original delegation to remain active. # Get external account Source: https://docs.natural.com/api-reference/external-accounts/get-external-account /api-reference/openapi.json get /external-accounts/{externalAccountId} Get a linked external account # Link external account Source: https://docs.natural.com/api-reference/external-accounts/link-external-account /api-reference/openapi.json post /external-accounts/processor-token Link or refresh a bank account using a Plaid processor token # List external accounts Source: https://docs.natural.com/api-reference/external-accounts/list-external-accounts /api-reference/openapi.json get /external-accounts List linked external accounts # Remove external account Source: https://docs.natural.com/api-reference/external-accounts/remove-external-account /api-reference/openapi.json delete /external-accounts/{externalAccountId} Remove a linked external account # Formats Source: https://docs.natural.com/api-reference/formats Data formats and units used across the API ## Monetary amounts All monetary amounts are represented as **integers in cents**. This avoids floating-point precision issues. | API value | Dollars | | --------- | ---------- | | `100` | \$1.00 | | `500000` | \$5,000.00 | | `1234` | \$12.34 | ```json theme={null} { "amount": 500000, "currency": "USD" } ``` Amounts are always integers, never strings or floats. Maximum precision is 2 decimal places (1 cent). Integer cents apply to the REST API and SDKs. Tools on the hosted [MCP server](/guides/platform/mcp#amounts-and-currencies) instead take decimal-string amounts with a required currency (`"amount": "10.50", "currency": "USD"`); the MCP server converts between the two encodings at the boundary. ## Currency codes [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) three-letter uppercase codes. ```json theme={null} { "currency": "USD" } ``` ## Timestamps [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) / [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339), always UTC. ```json theme={null} { "createdAt": "2026-01-04T15:30:00Z" } ``` Timestamps may include millisecond precision (`2026-01-04T15:30:00.123Z`). All timestamps use the `Z` suffix; the API does not return local time offsets. ## Phone numbers [E.164](https://en.wikipedia.org/wiki/E.164) international format: plus sign, country code, subscriber number. ```json theme={null} { "phone": "+14155551234" } ``` # Idempotency Source: https://docs.natural.com/api-reference/idempotency Ensuring safety when retrying a mutation Idempotency ensures retries are safe for mutating requests. Retrying the same request with the same `Idempotency-Key` never executes side effects twice. ## How it works Include an `Idempotency-Key` header on every request to an endpoint that requires one. Natural records the key alongside the outcome of the request, and a later request with the same key returns that recorded outcome instead of running the operation again. ```python theme={null} response = await client.post( "https://api.natural.com/payments", headers={ "Authorization": "Bearer sk_ntl_prod_abc123...", "Idempotency-Key": str(uuid.uuid4()) }, json={...} ) ``` On subsequent requests with the same key: * **Same key + same request + finished** -> Replays the recorded response. * **Same key + different request** -> Returns `409 conflict`. * **Same key + original still running** -> Returns `409 conflict`. * **Different key** -> Treated as a new mutation attempt. Replayed responses include `X-Idempotency-Replayed: true`. That header is the only thing distinguishing a replay from a fresh execution, so check it if you need to tell them apart. ## Endpoints that require a key The `Idempotency-Key` header is required on the endpoints below. Each one is a mutating operation whose accidental repetition would be visible to you or your customers. | Endpoint | Operation | Resource | | ------------------------------------------------------------------ | -------------------------------------------- | ----------------- | | `POST /agents` | Create agent | Agents | | `DELETE /agents/{agentId}` | Delete agent | Agents | | `PATCH /agents/{agentId}` | Update agent | Agents | | `POST /payments` | Create payment | Payments | | `POST /payments/{paymentId}/cancel` | Cancel payment | Payments | | `POST /transfers/deposit` | Initiate deposit | Transfers | | `POST /transfers/internal` | Initiate internal transfer | Transfers | | `POST /transfers/withdraw` | Initiate withdrawal | Transfers | | `POST /payment-requests` | Create payment request | PaymentRequests | | `POST /payment-requests/{paymentRequestId}/cancel` | Cancel payment request | PaymentRequests | | `POST /payment-requests/{paymentRequestId}/decline` | Decline payment request | PaymentRequests | | `POST /payment-requests/{paymentRequestId}/fulfill` | Fulfill payment request | PaymentRequests | | `POST /approvals/{approvalId}/approve` | Approve payment or transfer | Approvals | | `POST /approvals/{approvalId}/deny` | Deny payment or transfer | Approvals | | `DELETE /party-invitations/{invitationId}` | Revoke party invitation | Invitations | | `DELETE /parties/me/members/{userId}` | Remove party member | Parties | | `POST /wallets` | Create wallet | Wallets | | `PATCH /wallets/{walletId}` | Update wallet | Wallets | | `POST /wallets/{walletId}/agents` | Grant agent access to wallet | Wallets | | `DELETE /wallets/{walletId}/agents/{agentId}` | Detach agent from wallet | Wallets | | `POST /wallets/{walletId}/agents/{agentId}/default` | Set agent default wallet | Wallets | | `POST /wallets/{walletId}/default` | Set default wallet | Wallets | | `POST /external-accounts/processor-token` | Link external account | External Accounts | | `DELETE /api-keys/{keyId}` | Revoke API key | API Keys | | `DELETE /agent-keys/{keyId}` | Revoke agent key | Agent Keys | | `POST /agent-keys/{keyId}/rotate` | Rotate agent key | Agent Keys | | `POST /webhooks` | Create webhook | Webhooks | | `DELETE /webhooks/{webhookId}` | Delete webhook | Webhooks | | `PATCH /webhooks/{webhookId}` | Update webhook | Webhooks | | `POST /webhooks/{webhookId}/rotate-secret` | Rotate webhook signing secret | Webhooks | | `POST /webhooks/{webhookId}/events/{eventId}/redeliver` | Redeliver event | Events | | `POST /simulations/customer-invitations/{invitationId}/accept` | Accept customer invitation as test customer | Simulations | | `POST /simulations/customer-invitations/{invitationId}/decline` | Decline customer invitation as test customer | Simulations | | `POST /simulations/customers/{customerId}/agents/{agentId}/revoke` | Revoke agent access as test customer | Simulations | | `POST /simulations/customers/{customerId}/transfers/deposit` | Fund test customer | Simulations | | `POST /simulations/external-accounts/link` | Link test bank account | Simulations | | `POST /simulations/invite-customer` | Invite test customer | Simulations | | `POST /simulations/payment-requests/{paymentRequestId}/decline` | Decline payment request as test payer | Simulations | | `POST /simulations/payment-requests/{paymentRequestId}/fulfill` | Fulfill payment request as test payer | Simulations | Omitting the header on any of these returns `400` with the error code `invalid_value`. A key longer than 255 characters is rejected the same way. Endpoints not listed here ignore the header. ## Keys A key must be unique per logical operation and stable across every retry of that operation. Natural binds the key to the meaningful contents of the request it first arrives with, so the same key must always mean the same request. * Use a UUIDv4/UUIDv7, or any unique string of up to 255 characters. * Generate the key **once**, when the operation is first attempted, and reuse it for every retry of that attempt, including retries after a timeout. * Generate a **new** key only when the user or system starts a genuinely new operation. * Never derive a key from anything that changes between retries (a timestamp, an attempt counter, a per-request random value). A key that changes per retry provides no protection at all. * Don't put sensitive data in keys. Keys are scoped to the party the request acts on. Two parties can use the same key string without interfering, and a key is never shared across parties. ## Replay window A record is created when the request starts, and is finalized once the operation reaches a terminal outcome: success or failure. **The record then expires 48 hours after it finished**, not 48 hours after the request arrived. Within those 48 hours, the same key replays the recorded outcome. Once they elapse the key is forgotten, and reusing it starts an entirely new request, re-running the side effect. Treat 48 hours as the window in which a retry is guaranteed safe, not as a deduplication guarantee for the life of the key. A record for an operation that is still running does not expire on its own. If an operation never reaches a terminal outcome, its key stays reserved and requests reusing it keep returning `409 conflict`. Contact support if a key stays conflicted longer than you would expect. ## Conflicts Both conflict cases return `409` with the error code `conflict`: ```json theme={null} { "errors": [ { "code": "conflict", "detail": "The request conflicts with the current resource state.", "status": "409", "meta": { "supportId": "req_a1b2c3d4e5f6" } } ] } ``` The response body is identical in both cases, so tell them apart by what your own client did: * **You retried an identical request.** The original is still running. Wait, then retry the same key with exponential backoff. * **You changed the request but reused the key.** This is a client bug: a key is bound to the first request sent under it. Use a fresh key for the new operation. Include `meta.supportId` when contacting support about a conflict; the underlying reason is recorded against that ID. ## Failures are replayed too Failures are recorded exactly like successes. When an operation fails terminally, the same key replays that same error (identical code, status, and detail) for the rest of the window. Retrying a recorded failure with the same key does not re-run it. Two cases release the record rather than recording it, because neither reflects a decision about your request: `408` (timeout) and `429` (rate limited). Retrying either with the same key genuinely re-runs the operation. ## When to retry | Situation | Action | | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | Network failure or timeout with no response | Retry with the **same** key, exponential backoff. If the original landed you get its result; if it never did, it runs now | | `408` or `429` | Retry with the **same** key, exponential backoff. The record is released, so the retry re-runs | | Other `5xx` | Retry with the **same** key. You get the recorded error replayed, or a `409` while the original resolves | | `409 conflict` after an identical retry | The original is still running. Wait, then retry with the **same** key | | `409 conflict` after changing the request | Client bug. Use a **new** key for the new operation | | Other `4xx` | Don't retry the same key; it replays the error. Fix the request, then send it under a **new** key | Don't switch to a new key to force a failed operation to re-run. A new key is a new operation: if the original moved money before failing, a fresh key can move it a second time. Check the resource's status first, and only re-attempt under a new key once you have confirmed the original did not take effect. ## Related * [Error Handling](/api-reference/errors/error-handling) - Handling conflicts and other errors # Create invitation link Source: https://docs.natural.com/api-reference/invitation-links/create-invitation-link /api-reference/openapi.json post /customers/invitation-links Create a shareable link offering your agents, with the permissions and limits you set, to any customer who opens it # Get invitation link Source: https://docs.natural.com/api-reference/invitation-links/get-invitation-link /api-reference/openapi.json get /customers/invitation-links/{linkId} Get an invitation link and its offered agents, with a masked token # List invitation links Source: https://docs.natural.com/api-reference/invitation-links/list-invitation-links /api-reference/openapi.json get /customers/invitation-links List the invitation links you have created, with masked tokens # Revoke invitation link Source: https://docs.natural.com/api-reference/invitation-links/revoke-invitation-link /api-reference/openapi.json post /customers/invitation-links/{linkId}/revoke Revoke an invitation link so it stops connecting new customers # Create party invitations Source: https://docs.natural.com/api-reference/invitations/create-party-invitations /api-reference/openapi.json post /party-invitations Invite people to join the party. Available for business parties. # List party invitations Source: https://docs.natural.com/api-reference/invitations/list-party-invitations /api-reference/openapi.json get /party-invitations List party invitations # Revoke party invitation Source: https://docs.natural.com/api-reference/invitations/revoke-party-invitation /api-reference/openapi.json delete /party-invitations/{invitationId} Revoke a pending party invitation # Pagination Source: https://docs.natural.com/api-reference/pagination Cursor-based pagination for list endpoints List endpoints return paginated results using cursor-based pagination. Cursors are opaque strings; don't parse or construct them yourself. ## Parameters | Parameter | Default | Description | | --------- | ------- | ---------------------------------------------- | | `limit` | 50 | Number of results per page (1–100) | | `cursor` | — | Cursor from a previous response's `nextCursor` | `GET /customers` is the exception; its `limit` defaults to 20. ## Response structure Every list response includes pagination metadata in `meta`: ```json theme={null} { "data": [...], "meta": { "pagination": { "hasMore": true, "nextCursor": "eyJjcmVhdGVkQXQiOi..." } } } ``` * **`hasMore`** — `true` if additional results exist beyond this page * **`nextCursor`** — Pass this as the `cursor` query parameter to fetch the next page. `null` when `hasMore` is `false`. ## Related * [About the Natural API](/api-reference/about) — Response structure and JSON:API format # Disable party approval limits Source: https://docs.natural.com/api-reference/parties/disable-party-approval-limits /api-reference/openapi.json delete /parties/me/limits Disable all approval limits for the party # Get party Source: https://docs.natural.com/api-reference/parties/get-party /api-reference/openapi.json get /parties/me Get the current party # Get party approval limits Source: https://docs.natural.com/api-reference/parties/get-party-approval-limits /api-reference/openapi.json get /parties/me/limits Get the party's approval limits # Get party compliance Source: https://docs.natural.com/api-reference/parties/get-party-compliance /api-reference/openapi.json get /parties/{partyId}/compliance Get a party's current compliance status # List party members Source: https://docs.natural.com/api-reference/parties/list-party-members /api-reference/openapi.json get /parties/me/members List active members of the party. Available for business parties. # Remove party member Source: https://docs.natural.com/api-reference/parties/remove-party-member /api-reference/openapi.json delete /parties/me/members/{userId} Remove a member from the party # Set party approval limits Source: https://docs.natural.com/api-reference/parties/set-party-approval-limits /api-reference/openapi.json put /parties/me/limits Set the party's approval limits # Set party handle Source: https://docs.natural.com/api-reference/parties/set-party-handle /api-reference/openapi.json put /parties/me/handle Set or rename your party's handle; it can never be cleared # Update party Source: https://docs.natural.com/api-reference/parties/update-party /api-reference/openapi.json patch /parties/me Update the current party # Cancel payment request Source: https://docs.natural.com/api-reference/paymentrequests/cancel-payment-request /api-reference/openapi.json post /payment-requests/{paymentRequestId}/cancel Cancel an open outgoing payment request # Create payment request Source: https://docs.natural.com/api-reference/paymentrequests/create-payment-request /api-reference/openapi.json post /payment-requests Create a payment request # Decline payment request Source: https://docs.natural.com/api-reference/paymentrequests/decline-payment-request /api-reference/openapi.json post /payment-requests/{paymentRequestId}/decline Decline an open incoming payment request # Fulfill payment request Source: https://docs.natural.com/api-reference/paymentrequests/fulfill-payment-request /api-reference/openapi.json post /payment-requests/{paymentRequestId}/fulfill Fulfill an open payment request from a wallet or a verified linked bank account # Get payment request Source: https://docs.natural.com/api-reference/paymentrequests/get-payment-request /api-reference/openapi.json get /payment-requests/{paymentRequestId} Get a payment request # List incoming payment requests Source: https://docs.natural.com/api-reference/paymentrequests/list-incoming-payment-requests /api-reference/openapi.json get /payment-requests/incoming List incoming payment requests # List payment requests Source: https://docs.natural.com/api-reference/paymentrequests/list-payment-requests /api-reference/openapi.json get /payment-requests List outgoing payment requests # Cancel payment Source: https://docs.natural.com/api-reference/payments/cancel-payment /api-reference/openapi.json post /payments/{paymentId}/cancel Cancel a payment before the recipient begins claiming it # Create payment Source: https://docs.natural.com/api-reference/payments/create-payment /api-reference/openapi.json post /payments Create a payment # Get payment Source: https://docs.natural.com/api-reference/payments/get-payment /api-reference/openapi.json get /payments/{paymentId} Get a sent payment # List payments Source: https://docs.natural.com/api-reference/payments/list-payments /api-reference/openapi.json get /payments List sent payments # Rate limits Source: https://docs.natural.com/api-reference/rate-limits API request rate limiting Rate limits use a token bucket algorithm. Authenticated requests are limited per credential; unauthenticated endpoints are limited per client IP address. ## Rate limit headers Responses include the current limit state: | Header | Description | | ----------------------- | ---------------------------------------------- | | `X-RateLimit-Limit` | Maximum requests allowed in the current window | | `X-RateLimit-Remaining` | Requests remaining in the current window | | `X-RateLimit-Reset` | Unix epoch seconds when the window resets | A `429` response also includes a `Retry-After` header giving the number of seconds to wait before retrying. ## Rate limit response When you exceed the rate limit, the API returns `429 Too Many Requests`: ```json theme={null} { "errors": [ { "code": "rate_limited", "detail": "Too many requests. Please try again later.", "status": "429", "meta": { "supportId": "req_a1b2c3d4e5f6" } } ] } ``` ## Handling rate limits Back off and retry with exponential delay: ```python theme={null} import asyncio import httpx async def request_with_backoff(client: httpx.AsyncClient, url: str, **kwargs): max_retries = 3 for attempt in range(max_retries): response = await client.get(url, **kwargs) if response.status_code != 429: return response # Honor Retry-After when present, else fall back to exponential backoff. retry_after = response.headers.get("Retry-After") wait = int(retry_after) if retry_after else 2 ** attempt await asyncio.sleep(wait) return response ``` ## Best practices * **Batch where possible.** Fewer large requests beat many small ones. * **Cache responses** that don't change frequently (e.g., party details, wallet balance). * **Implement exponential backoff** on `429` responses. ## Related * [Error Handling](/api-reference/errors/error-handling) — Error response format * [Idempotency](/api-reference/idempotency) — Safe retries after rate limiting # Sandbox fixtures Source: https://docs.natural.com/api-reference/sandbox/fixtures Reserved test identifiers and synthetic counterparties for your sandbox organization A fixture is a synthetic counterparty that only your sandbox organization can see. Fixtures stand in for the other side of every flow: the recipient of your payment, the payer of your request, the customer who authorizes your agents. ## Send a payment Pay any of these identifiers and your recipient fixture receives it: | Identifier | Value | | ---------- | ---------------------------------------- | | Email | `payment-recipient@sandbox.natural.test` | | Phone | `+14155550101` | | Handle | `@sandbox.payment_recipient` | ## Request a payment Address a payment request to the payer fixture, then [fulfill](/api-reference/simulations/fulfill-payment-request) or [decline](/api-reference/simulations/decline-payment-request) it as the payer. The payer is always funded. | Identifier | Value | | ---------- | -------------------------------------------- | | Email | `payment-request-payer@sandbox.natural.test` | | Phone | `+14155550102` | | Handle | `@sandbox.request_payer` | ## Pay someone who hasn't joined yet Payments to these identifiers wait as claims, just as they would for a real person without a Natural account. [Complete the claim](/api-reference/simulations/complete-payment-claim), or call `simulate_payment_claim_completion` through MCP, to simulate the payee joining and receiving the funds. | Identifier | Value | | ---------- | ------------------------------------ | | Email | `payment-claim@sandbox.natural.test` | | Phone | `+14155550103` | ## Simulate Connect [Connect](/guides/products/connect) is how customers authorize your agents to act for them. [Invite a test customer](/api-reference/simulations/create-test-customer) by choosing its agents. Natural creates the synthetic customer, visible only to your organization, and leaves one invitation `PENDING` for each agent; no customer contact details are required. [Accept](/api-reference/simulations/accept-customer-invitation) or [decline](/api-reference/simulations/decline-customer-invitation) each returned invitation as the test customer. After acceptance, [fund the customer](/api-reference/simulations/fund-test-customer), pay on its behalf with `customerPartyId`, and [revoke access](/api-reference/simulations/revoke-agent-connection) when a test needs a clean slate. # Sandbox Source: https://docs.natural.com/api-reference/sandbox/overview Experience Natural without moving real money The Natural dashboard in sandbox mode The sandbox is a complete Natural environment that functions like production. Point your integration at the sandbox base URL with a sandbox key: ```text theme={null} https://api.sandbox.natural.com ``` Keys carry their environment in the prefix (`sk_ntl_sandbox_...`, `ak_ntl_sandbox_...`) and never work anywhere else. Sandbox keys come from the [dashboard](/guides/platform/dashboard): open the account menu in the bottom-left corner (the same menu you sign out from) and flip the **Sandbox** toggle. ## How it behaves * **No separate onboarding.** Verified on Natural means verified in the sandbox: your organization carries over automatically, and the test parties you create are auto-verified. * **Money moves instantly.** Transfers and payments settle as soon as you make them. * You can reach your own parties and your [fixtures](/api-reference/sandbox/fixtures), nothing else. No other sandbox organization can see or touch your data. ## What you can do | Surface | What you can do | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Connect | [Invite a test customer](/api-reference/simulations/create-test-customer), [accept](/api-reference/simulations/accept-customer-invitation) or [decline](/api-reference/simulations/decline-customer-invitation) the pending invitation, then [fund it](/api-reference/simulations/fund-test-customer) or [revoke access](/api-reference/simulations/revoke-agent-connection) | | Payments | [Complete a payment claim](/api-reference/simulations/complete-payment-claim) held for an unregistered recipient | | Payment requests | [Fulfill](/api-reference/simulations/fulfill-payment-request) or [decline](/api-reference/simulations/decline-payment-request) a request as the payer | | Bank accounts | [Link a test bank account](/api-reference/simulations/link-test-bank-account) to your own party | Sandbox endpoints sit next to the resources they test, marked with a `Sandbox:` prefix. Every action above is also available from MCP, the SDKs, and the CLI; see [Sandbox from MCP, CLI, and SDKs](/api-reference/sandbox/surfaces). # Sandbox from MCP, CLI, and SDKs Source: https://docs.natural.com/api-reference/sandbox/surfaces Connect to the Natural Sandbox To start with the sandbox environment in Natural, get a sandbox key (`sk_ntl_sandbox_…`, `ak_ntl_sandbox_…`) by switching the [dashboard](/guides/platform/dashboard) to Sandbox. Simulation actions, the counterparty side of each flow, are first-class on every surface: | Surface | Where simulations live | | -------- | -------------------------------------------------------------------------------------------------------------- | | REST API | [`POST /simulations/*`](/api-reference/simulations/create-test-customer), grouped with the resources they test | | SDKs | `client.simulations` in [TypeScript and Python](/guides/platform/sdks) | | CLI | [`natural simulations `](/guides/platform/cli) commands | | MCP | Sandbox-only tools on `mcp.sandbox.natural.com` | ## SDKs Point the client at the sandbox and put your agent's sandbox key in `NATURAL_API_KEY`. Your integration code is unchanged; `client.simulations` acts as the counterparty. ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; // NATURAL_API_KEY=ak_ntl_sandbox_... const client = new Natural({ instanceId: "sandbox-run-1", baseUrl: "https://api.sandbox.natural.com", }); // Invite a test customer; one pending invitation is created per agent const invited = await client.simulations.inviteCustomer({ agentIds: ["agt_550e8400e29b41d4a716446655440000"], idempotencyKey: "test-customer-1", }); const customerId = invited.meta.customer.partyId; // Accept the invitation as the test customer await client.simulations.acceptCustomerInvitation({ invitationId: invited.data[0].id, partyId: customerId, idempotencyKey: "accept-invitation-1", }); // Fund it so payments on its behalf can settle await client.simulations.fundCustomer({ customerId, amount: 250000, // cents - $2,500.00 currency: "USD", idempotencyKey: "fund-customer-1", }); // Run your integration exactly as it runs in production const payment = await client.payments.create({ amount: 10000, currency: "USD", counterparty: { type: "email", value: "payment-recipient@sandbox.natural.test" }, customerPartyId: customerId, idempotencyKey: "pay-1", }); ``` ```python Python theme={null} from naturalpay import Natural # NATURAL_API_KEY=ak_ntl_sandbox_... client = Natural( instance_id="sandbox-run-1", base_url="https://api.sandbox.natural.com", ) # Invite a test customer; one pending invitation is created per agent invited = client.simulations.invite_customer( agent_ids=["agt_550e8400e29b41d4a716446655440000"], idempotency_key="test-customer-1", ) customer_id = invited.meta.customer.party_id # Accept the invitation as the test customer client.simulations.accept_customer_invitation( invitation_id=invited.data[0].id, party_id=customer_id, idempotency_key="accept-invitation-1", ) # Fund it so payments on its behalf can settle client.simulations.fund_customer( customer_id=customer_id, amount=250000, # cents - $2,500.00 currency="USD", idempotency_key="fund-customer-1", ) # Run your integration exactly as it runs in production payment = client.payments.create( amount=10000, currency="USD", counterparty={"type": "email", "value": "payment-recipient@sandbox.natural.test"}, customer_party_id=customer_id, idempotency_key="pay-1", ) ``` Both SDKs expose the full simulation surface. TypeScript names methods in camelCase, Python in snake\_case: | Method | Simulates | | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `inviteCustomer` | [A test customer with a pending invitation per agent](/api-reference/simulations/create-test-customer) | | `fundCustomer` | [A deposit into a test customer's wallet](/api-reference/simulations/fund-test-customer) | | `linkExternalAccount` | [A linked test bank account](/api-reference/simulations/link-test-bank-account) | | `completePaymentClaim` | [An unregistered payee claiming your payment](/api-reference/simulations/complete-payment-claim) | | `fulfillPaymentRequest` / `declinePaymentRequest` | [The payer answering your request](/api-reference/simulations/fulfill-payment-request) | | `acceptCustomerInvitation` / `declineCustomerInvitation` | [The customer answering your invitation](/api-reference/simulations/accept-customer-invitation) | | `revokeAgentConnection` | [The customer revoking your agent](/api-reference/simulations/revoke-agent-connection) | ## CLI Export a sandbox key and the sandbox base URL; every command then runs against the sandbox: ```bash theme={null} export NATURAL_API_KEY=sk_ntl_sandbox_abc123... export NATURAL_BASE_URL=https://api.sandbox.natural.com # Invite a test customer; the response carries the customer party ID and invitation IDs natural simulations inviteCustomer \ --json '{"agentIds": ["agt_019cd1798d637a4da75dce386343931d"]}' \ --idempotency-key "$(uuidgen)" # Accept the invitation as the test customer natural simulations acceptCustomerInvitation \ --invitation-id adi_019cd1798d637a4da75dce386343931d \ --json '{"partyId": "pty_7c9e6679e29b41d4a716446655440001"}' \ --idempotency-key "$(uuidgen)" # Fund it, then pay on its behalf exactly as in production natural simulations fundCustomer \ --customer-id pty_7c9e6679e29b41d4a716446655440001 \ --amount 250000 \ --currency USD \ --idempotency-key "$(uuidgen)" ``` `--base-url` works per command if you prefer not to export `NATURAL_BASE_URL`. `natural login` signs into production. Against the sandbox, authenticate with a sandbox key. ## MCP Natural runs a sandbox peer of the hosted MCP server at **`https://mcp.sandbox.natural.com`**. Connect it exactly like the [production server](/guides/platform/mcp), alongside it if you like: ```json theme={null} { "mcpServers": { "natural": { "url": "https://mcp.natural.com" }, "natural-sandbox": { "url": "https://mcp.sandbox.natural.com" } } } ``` The sandbox peer exposes every production tool except `create_external_account_from_processor_token`, which `link_test_bank_account` replaces without needing a Plaid processor token. On this peer, `invite_customer` creates a synthetic test customer with pending invitations instead of emailing a real person. Below are the tools that are sandbox-only: | Tool | What it does | | -------------------------------------- | ----------------------------------------------------- | | `simulate_customer_deposit` | Add test funds to a connected customer | | `link_test_bank_account` | Link a synthetic bank account to your party | | `simulate_payment_claim_completion` | Complete a held payment as the claim recipient | | `simulate_payment_request_fulfillment` | Fulfill your payment request as the test payer | | `simulate_payment_request_decline` | Decline your payment request as the test payer | | `simulate_customer_invitation_accept` | Accept your customer invitation as the test customer | | `simulate_customer_invitation_decline` | Decline your customer invitation as the test customer | | `simulate_agent_connection_revoke` | Revoke an agent's access as the test customer | # Sandbox: Accept a Connect invitation Source: https://docs.natural.com/api-reference/simulations/accept-customer-invitation POST /simulations/customer-invitations/{invitationId}/accept Accept a pending customer invitation as the test customer it is addressed to Accepts a pending [Connect](/guides/products/connect) invitation as the invited [test customer](/api-reference/sandbox/fixtures#simulate-connect). The agent gains access exactly as if a real customer had accepted. # Sandbox: Complete a payment claim Source: https://docs.natural.com/api-reference/simulations/complete-payment-claim POST /simulations/payments/{paymentId}/complete-claim Simulate the recipient of a held payment joining Natural and receiving the funds Completes a payment held for the [claim recipient](/api-reference/sandbox/fixtures#pay-someone-who-hasnt-joined-yet), as if the payee just joined Natural and received the funds. From the Sandbox MCP server, use `create_payment` with `payment-claim@sandbox.natural.test`, then pass the returned payment ID to `simulate_payment_claim_completion`. # Sandbox: Invite a test customer Source: https://docs.natural.com/api-reference/simulations/create-test-customer POST /simulations/invite-customer Create a test customer and a pending invitation for each agent you name Creates a synthetic [Connect](/guides/products/connect) customer, visible only to your organization, and one `PENDING` invitation for every agent you name. You provide agents only; no customer email, phone, or identity details. Use the returned customer Party ID and invitation IDs to [accept](/api-reference/simulations/accept-customer-invitation) or [decline](/api-reference/simulations/decline-customer-invitation) each invitation. The customer grants no agent access and holds no funds until an invitation is accepted. See [Simulate Connect](/api-reference/sandbox/fixtures#simulate-connect). # Sandbox: Decline a Connect invitation Source: https://docs.natural.com/api-reference/simulations/decline-customer-invitation POST /simulations/customer-invitations/{invitationId}/decline Decline a pending customer invitation as the test customer it is addressed to Declines a pending [Connect](/guides/products/connect) invitation as the invited [test customer](/api-reference/sandbox/fixtures#simulate-connect). The agent gains no access. # Sandbox: Decline a payment request Source: https://docs.natural.com/api-reference/simulations/decline-payment-request POST /simulations/payment-requests/{paymentRequestId}/decline Decline an open payment request addressed to your test payer Declines a request addressed to your [test payer](/api-reference/sandbox/fixtures#request-a-payment). No money moves. # Sandbox: Fulfill a payment request Source: https://docs.natural.com/api-reference/simulations/fulfill-payment-request POST /simulations/payment-requests/{paymentRequestId}/fulfill Fulfill an open payment request addressed to your test payer Pays a request addressed to your [test payer](/api-reference/sandbox/fixtures#request-a-payment) from the fixture's funded wallet. # Sandbox: Fund a Connect customer Source: https://docs.natural.com/api-reference/simulations/fund-test-customer POST /simulations/customers/{customerId}/transfers/deposit Add test funds to a connected customer's wallet Gives a [Connect customer](/api-reference/sandbox/fixtures#simulate-connect) a balance so your agents can move money on its behalf. # Sandbox: Link a test bank account Source: https://docs.natural.com/api-reference/simulations/link-test-bank-account POST /simulations/external-accounts/link Link a test bank account to your own party Links a test bank account to your own party. It behaves like any [external account](/guides/concepts/external-accounts) for deposit and withdrawal testing. # Sandbox: Revoke Connect access Source: https://docs.natural.com/api-reference/simulations/revoke-agent-connection POST /simulations/customers/{customerId}/agents/{agentId}/revoke Remove an agent's access to a test customer, acting as the customer Ends an agent's access to a [Connect customer](/api-reference/sandbox/fixtures#simulate-connect), acting as the customer who granted it. # Get transaction Source: https://docs.natural.com/api-reference/transactions/get-transaction /api-reference/openapi.json get /transactions/{transactionId} Get a transaction # List transactions Source: https://docs.natural.com/api-reference/transactions/list-transactions /api-reference/openapi.json get /transactions List transactions # Get transfer Source: https://docs.natural.com/api-reference/transfers/get-transfer /api-reference/openapi.json get /transfers/{transferId} Get a transfer # Initiate deposit Source: https://docs.natural.com/api-reference/transfers/initiate-deposit /api-reference/openapi.json post /transfers/deposit Move funds from a linked bank account into a wallet # Initiate internal transfer Source: https://docs.natural.com/api-reference/transfers/initiate-internal-transfer /api-reference/openapi.json post /transfers/internal Move funds between two wallets # Initiate withdrawal Source: https://docs.natural.com/api-reference/transfers/initiate-withdrawal /api-reference/openapi.json post /transfers/withdraw Move funds from a wallet to a linked bank account # List transfers Source: https://docs.natural.com/api-reference/transfers/list-transfers /api-reference/openapi.json get /transfers List transfers # Create wallet Source: https://docs.natural.com/api-reference/wallets/create-wallet /api-reference/openapi.json post /wallets Create a standard wallet # Detach agent from wallet Source: https://docs.natural.com/api-reference/wallets/detach-agent-from-wallet /api-reference/openapi.json delete /wallets/{walletId}/agents/{agentId} Detach an agent from a wallet # Freeze wallet Source: https://docs.natural.com/api-reference/wallets/freeze-wallet /api-reference/openapi.json post /wallets/{walletId}/freeze Freeze a wallet # Get wallet Source: https://docs.natural.com/api-reference/wallets/get-wallet /api-reference/openapi.json get /wallets/{walletId} Get a wallet # Grant agent access to wallet Source: https://docs.natural.com/api-reference/wallets/grant-agent-access-to-wallet /api-reference/openapi.json post /wallets/{walletId}/agents Grant an agent access to a wallet # List wallet agents Source: https://docs.natural.com/api-reference/wallets/list-wallet-agents /api-reference/openapi.json get /wallets/{walletId}/agents List agents granted access to a wallet # List wallets Source: https://docs.natural.com/api-reference/wallets/list-wallets /api-reference/openapi.json get /wallets List wallets # Set agent default wallet Source: https://docs.natural.com/api-reference/wallets/set-agent-default-wallet /api-reference/openapi.json post /wallets/{walletId}/agents/{agentId}/default Make this wallet the default for the attached agent # Set default wallet Source: https://docs.natural.com/api-reference/wallets/set-default-wallet /api-reference/openapi.json post /wallets/{walletId}/default Set the party's default wallet, where payments addressed to their email, phone number, or handle land # Unfreeze wallet Source: https://docs.natural.com/api-reference/wallets/unfreeze-wallet /api-reference/openapi.json post /wallets/{walletId}/unfreeze Unfreeze a wallet that was frozen through the API # Update wallet Source: https://docs.natural.com/api-reference/wallets/update-wallet /api-reference/openapi.json patch /wallets/{walletId} Update a wallet's display name, description, or tags # Create webhook Source: https://docs.natural.com/api-reference/webhooks/create-webhook /api-reference/openapi.json post /webhooks Create a webhook endpoint. The signing secret is returned only once. # Delete webhook Source: https://docs.natural.com/api-reference/webhooks/delete-webhook /api-reference/openapi.json delete /webhooks/{webhookId} Delete a webhook endpoint # Get webhook Source: https://docs.natural.com/api-reference/webhooks/get-webhook /api-reference/openapi.json get /webhooks/{webhookId} Get a webhook endpoint # List webhooks Source: https://docs.natural.com/api-reference/webhooks/list-webhooks /api-reference/openapi.json get /webhooks List webhook endpoints # Rotate webhook signing secret Source: https://docs.natural.com/api-reference/webhooks/rotate-webhook-signing-secret /api-reference/openapi.json post /webhooks/{webhookId}/rotate-secret Generate a new signing secret. # Update webhook Source: https://docs.natural.com/api-reference/webhooks/update-webhook /api-reference/openapi.json patch /webhooks/{webhookId} Update a webhook endpoint # Create an agent Source: https://docs.natural.com/guides/agents/create-agent Create an agent and get back its agent ID An agent is an actor that moves money for you. Every payment, request, deposit, and withdrawal is attributed to its ID, so create one before you move any money. Create an agent with [`POST /agents`](/api-reference/agents/create-agent). ```python Python theme={null} import uuid from naturalpay import Natural from naturalpay.agents import CreateAgentsRequestLimits client = Natural() agent = client.agents.create( name="Procurement Agent", slug="procurementagent", description="Buys supplies", limits=CreateAgentsRequestLimits(per_transaction=100_000), idempotency_key=str(uuid.uuid4()), ) agent_id = agent.data.id ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const agent = await client.agents.create({ name: "Procurement Agent", slug: "procurementagent", description: "Buys supplies", limits: { perTransaction: 100_000 }, idempotencyKey: crypto.randomUUID(), }); const agentId = agent.data.id; ``` ```bash CLI theme={null} natural agents create --json '{ "name": "Procurement Agent", "slug": "procurementagent", "description": "Buys supplies", "limits": { "perTransaction": 100000 } }' --idempotency-key "$(uuidgen)" ``` ```text MCP theme={null} Create an agent called "Procurement Agent" with slug procurementagent for buying supplies, with a $1,000 per-transaction limit. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/agents \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "name": "Procurement Agent", "slug": "procurementagent", "description": "Buys supplies", "limits": { "perTransaction": 100000 } } } }' ``` Store the returned agent ID (`agt_*`) and pass it whenever this agent moves money, so Natural attributes the money movement to it. ## Issue a key Creating an agent returns its ID, not a credential. Issue a key next so your runtime can authenticate as that agent with `ak_ntl_*`. The dashboard runs [`POST /agents`](/api-reference/agents/create-agent) and [`POST /agent-keys`](/api-reference/agent-keys/create-agent-key) back to back; over the API you do the same. ```python Python theme={null} key = client.agent_keys.create( data={ "attributes": {}, "relationships": { "agent": {"data": {"type": "agent", "id": agent_id}}, }, }, ) print(key.data.attributes.agent_key) ``` ```typescript TypeScript theme={null} const key = await client.agentKeys.create({ data: { attributes: {}, relationships: { agent: { data: { type: "agent", id: agentId } }, }, }, }); console.log(key.data.attributes.agentKey); ``` ```bash CLI theme={null} natural agent-keys create --agent-id agt_019cd1798d637a4da75dce386343931d ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/agent-keys \ -H "Authorization: Bearer $NATURAL_USER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": {}, "relationships": { "agent": { "data": { "type": "agent", "id": "agt_019cd1798d637a4da75dce386343931d" } } } } }' ``` Issue keys from a user session only, not from a party API key. The full secret is returned once, so store it in your secrets manager immediately. Afterward you can see only its prefix. To list, rotate, or revoke keys on an existing agent, see [Manage your agents](/guides/agents/manage-agents#agent-keys). # Handles Source: https://docs.natural.com/guides/agents/handles Claim a @handle to give you and your agents an identity Claim `@acme` and anyone on Natural can interact with you by name. Each agent gets its own handle under your party, for example `@acme-procurementagent`. ## Claim a handle [`PUT /parties/me/handle`](/api-reference/parties/set-party-handle) sets your handle. Send the bare name (`acme`), not `@acme`. The fastest path is the dashboard, under **Settings → Profile**. ```python Python theme={null} party = client.parties.set_handle(handle="acme") print(party.data.attributes.handle) ``` ```typescript TypeScript theme={null} const party = await client.parties.setHandle({ handle: "acme" }); console.log(party.data.attributes.handle); ``` ```bash CLI theme={null} natural parties setHandle --handle acme ``` ```bash cURL theme={null} curl -X PUT https://api.natural.com/parties/me/handle \ -H "Authorization: Bearer $NATURAL_SESSION_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "handle": "acme" } } }' ``` ## Agent handles Each agent gets its own handle: its handle composes with your party handle as `@party-agent`. Set the handle when you create the agent, on [`POST /agents`](/api-reference/agents/create-agent). ```python Python theme={null} import uuid agent = client.agents.create( name="Procurement Agent", slug="procurementagent", idempotency_key=str(uuid.uuid4()), ) print(agent.data.id) ``` ```typescript TypeScript theme={null} const agent = await client.agents.create({ name: "Procurement Agent", slug: "procurementagent", idempotencyKey: crypto.randomUUID(), }); console.log(agent.data.id); ``` ```bash CLI theme={null} natural agents create --name "Procurement Agent" --slug procurementagent --idempotency-key "$(uuidgen)" ``` ```text MCP theme={null} Create a procurement agent named "Procurement Agent" with the handle slug procurementagent. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/agents \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "data": { "attributes": { "name": "Procurement Agent", "slug": "procurementagent" } } }' ``` Once your party has a handle and the agent has a slug, others pay or request that agent directly at `@acme-procurementagent`. ## Pay by handle Pay someone at their `@handle` with [`POST /payments`](/api-reference/payments/create-payment) and a `handle` counterparty. The handle resolves to an existing party, so funds route straight to their wallet with no claim link. The leading `@` is optional. See [Send a payment](/guides/payments/send-payment) for the full payment mechanics and every counterparty type. ```python Python theme={null} import uuid from naturalpay import Natural agent_client = Natural( x_agent_id="agt_019cd1798d637a4da75dce386343931d", x_instance_id=str(uuid.uuid4()), ) payment = agent_client.payments.create( amount=500_000, currency="USD", counterparty={"type": "handle", "value": "@natural-contractor"}, description="Q4 development work", idempotency_key=str(uuid.uuid4()), ) print(payment.data.id) ``` ```typescript TypeScript theme={null} const payment = await client.payments.create( { amount: 500_000, currency: "USD", counterparty: { type: "handle", value: "@natural-contractor" }, description: "Q4 development work", idempotencyKey: crypto.randomUUID(), }, { xAgentId: "agt_019cd1798d637a4da75dce386343931d", xInstanceId: crypto.randomUUID(), }, ); console.log(payment.data.id); ``` ```bash CLI theme={null} natural payments create \ --amount 500000 \ --currency USD \ --params '{"counterparty": {"type": "handle", "value": "@natural-contractor"}}' \ --description "Q4 development work" \ --x-agent-id agt_019cd1798d637a4da75dce386343931d \ --x-instance-id "$(uuidgen)" \ --idempotency-key "$(uuidgen)" ``` ```text MCP theme={null} Using my Procurement Agent, pay @natural-contractor $5,000 for Q4 development work. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/payments \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "X-Agent-ID: agt_019cd1798d637a4da75dce386343931d" \ -H "X-Instance-ID: $(uuidgen)" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "amount": 500000, "currency": "USD", "counterparty": { "type": "handle", "value": "@natural-contractor" }, "description": "Q4 development work" } } }' ``` ## Request by handle Collect from someone at their `@handle` with [`POST /payment-requests`](/api-reference/paymentrequests/create-payment-request) and a `handle` payer. Because the handle resolves to a known party, they get a link to the request in their dashboard rather than a claim link. Natural sends that link by email, falling back to text when the party has no email on file, and the request also lands in their incoming requests and fires a `payment_request.incoming` webhook. See [Request a payment](/guides/payments/request-payment) to track the request or cancel it. ```python Python theme={null} import uuid from naturalpay import Natural agent_client = Natural( x_agent_id="agt_019cd1798d637a4da75dce386343931d", x_instance_id=str(uuid.uuid4()), ) request = agent_client.payment_requests.create( amount=2500, currency="USD", description="Invoice 7", payer_name="Natural Client", payer={"type": "handle", "value": "@natural-client"}, idempotency_key=str(uuid.uuid4()), ) print(request.data.id) ``` ```typescript TypeScript theme={null} const request = await client.paymentRequests.create( { amount: 2500, currency: "USD", description: "Invoice 7", payerName: "Natural Client", payer: { type: "handle", value: "@natural-client" }, idempotencyKey: crypto.randomUUID(), }, { xAgentId: "agt_019cd1798d637a4da75dce386343931d", xInstanceId: crypto.randomUUID(), }, ); console.log(request.data.id); ``` ```bash CLI theme={null} natural payment-requests create \ --amount 2500 \ --currency USD \ --description "Invoice 7" \ --payer-name "Natural Client" \ --params '{"payer": {"type": "handle", "value": "@natural-client"}}' \ --x-agent-id agt_019cd1798d637a4da75dce386343931d \ --x-instance-id "$(uuidgen)" \ --idempotency-key "$(uuidgen)" ``` ```text MCP theme={null} Using my Procurement Agent, request $25 from @natural-client for Invoice 7. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/payment-requests \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "X-Agent-ID: agt_019cd1798d637a4da75dce386343931d" \ -H "X-Instance-ID: $(uuidgen)" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "amount": 2500, "currency": "USD", "description": "Invoice 7", "payerName": "Natural Client", "payer": { "type": "handle", "value": "@natural-client" } } } }' ``` # Manage your agents Source: https://docs.natural.com/guides/agents/manage-agents List and edit your agents See every agent you have created, inspect one, rename it, or revoke it when it is done. ## List your agents List every agent on your account with [`GET /agents`](/api-reference/agents/list-agents). ```python Python theme={null} from naturalpay import Natural client = Natural() agents = client.agents.list() ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const agents = await client.agents.list(); ``` ```bash CLI theme={null} natural agents list ``` ```text MCP theme={null} List my agents. ``` ```bash cURL theme={null} curl https://api.natural.com/agents \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` ## Inspect one agent Inspect one agent by ID with [`GET /agents/{agentId}`](/api-reference/agents/get-agent). ```python Python theme={null} agent = client.agents.get("agt_019cd1798d637a4da75dce386343931d") ``` ```typescript TypeScript theme={null} const agent = await client.agents.get({ agentId: "agt_019cd1798d637a4da75dce386343931d", }); ``` ```bash CLI theme={null} natural agents get --agent-id agt_019cd1798d637a4da75dce386343931d ``` ```text MCP theme={null} Show me my Procurement Agent. ``` ```bash cURL theme={null} curl https://api.natural.com/agents/agt_019cd1798d637a4da75dce386343931d \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` ## Rename an agent Rename an agent, change its description, or adjust its [limits](/guides/controls/limits), with [`PATCH /agents/{agentId}`](/api-reference/agents/update-agent). ```python Python theme={null} import uuid agent = client.agents.update( "agt_019cd1798d637a4da75dce386343931d", idempotency_key=str(uuid.uuid4()), name="Procurement Agent v2.1", description="Autonomous agent that pays contractors", ) ``` ```typescript TypeScript theme={null} const agent = await client.agents.update({ agentId: "agt_019cd1798d637a4da75dce386343931d", idempotencyKey: crypto.randomUUID(), name: "Procurement Agent v2.1", description: "Autonomous agent that pays contractors", }); ``` ```bash CLI theme={null} natural agents update --agent-id agt_019cd1798d637a4da75dce386343931d \ --name "Procurement Agent v2.1" \ --description "Autonomous agent that pays contractors" \ --idempotency-key "$(uuidgen)" ``` ```bash cURL theme={null} curl -X PATCH https://api.natural.com/agents/agt_019cd1798d637a4da75dce386343931d \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "name": "Procurement Agent v2.1", "description": "Autonomous agent that pays contractors" } } }' ``` ## Revoke an agent Revoke an agent with [`DELETE /agents/{agentId}`](/api-reference/agents/delete-agent). Its status becomes `REVOKED` and it stops moving money at once, but the record stays readable so its history survives. ```python Python theme={null} import uuid client.agents.remove( "agt_019cd1798d637a4da75dce386343931d", idempotency_key=str(uuid.uuid4()), ) ``` ```typescript TypeScript theme={null} await client.agents.remove({ agentId: "agt_019cd1798d637a4da75dce386343931d", idempotencyKey: crypto.randomUUID(), }); ``` ```bash CLI theme={null} natural agents remove --agent-id agt_019cd1798d637a4da75dce386343931d \ --idempotency-key "$(uuidgen)" ``` ```bash cURL theme={null} curl -X DELETE https://api.natural.com/agents/agt_019cd1798d637a4da75dce386343931d \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` ## Agent keys An agent key (`ak_ntl_*`) is a credential bound to one agent. Your dashboard lists keys on the agent detail page; over the API, use the agent-keys endpoints below. Issue a new key when you [create an agent](/guides/agents/create-agent#issue-a-key). ### List keys There is no get-by-id endpoint. List keys with [`GET /agent-keys`](/api-reference/agent-keys/list-agent-keys), optionally filtered by agent. ```python Python theme={null} keys = client.agent_keys.list(agent_id="agt_019cd1798d637a4da75dce386343931d") ``` ```typescript TypeScript theme={null} const keys = await client.agentKeys.list({ agentId: "agt_019cd1798d637a4da75dce386343931d" }); ``` ```bash CLI theme={null} natural agent-keys list --agent-id agt_019cd1798d637a4da75dce386343931d ``` ```bash cURL theme={null} curl "https://api.natural.com/agent-keys?agentId=agt_019cd1798d637a4da75dce386343931d" \ -H "Authorization: Bearer $NATURAL_USER_TOKEN" ``` ### Rotate a key Rotate with [`POST /agent-keys/{keyId}/rotate`](/api-reference/agent-keys/rotate-agent-key) to mint a fresh secret on the same agent. Set a grace period so running workloads can pick up the new key before the old one stops working. The new secret is again shown once. ```python Python theme={null} import uuid rotated = client.agent_keys.rotate( key_id="agk_550e8400e29b41d4a716446655440000", data={"attributes": {"expiresInSeconds": 86400}}, idempotency_key=str(uuid.uuid4()), ) ``` ```typescript TypeScript theme={null} const rotated = await client.agentKeys.rotate({ keyId: "agk_550e8400e29b41d4a716446655440000", data: { attributes: { expiresInSeconds: 86400 } }, idempotencyKey: crypto.randomUUID(), }); ``` ```bash CLI theme={null} natural agent-keys rotate \ --key-id agk_550e8400e29b41d4a716446655440000 \ --expires-in-seconds 86400 \ --idempotency-key "$(uuidgen)" ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/agent-keys/agk_550e8400e29b41d4a716446655440000/rotate \ -H "Authorization: Bearer $NATURAL_USER_TOKEN" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "expiresInSeconds": 86400 } } }' ``` ### Revoke a key Revoke with [`DELETE /agent-keys/{keyId}`](/api-reference/agent-keys/revoke-agent-key) to invalidate a key immediately. This cannot be undone, and other keys on the same agent keep working. Reach for it when a secret leaks or you are retiring one credential without archiving the agent. ```python Python theme={null} import uuid client.agent_keys.revoke( key_id="agk_550e8400e29b41d4a716446655440000", idempotency_key=str(uuid.uuid4()), ) ``` ```typescript TypeScript theme={null} await client.agentKeys.revoke({ keyId: "agk_550e8400e29b41d4a716446655440000", idempotencyKey: crypto.randomUUID(), }); ``` ```bash CLI theme={null} natural agent-keys revoke \ --key-id agk_550e8400e29b41d4a716446655440000 \ --idempotency-key "$(uuidgen)" ``` ```bash cURL theme={null} curl -X DELETE https://api.natural.com/agent-keys/agk_550e8400e29b41d4a716446655440000 \ -H "Authorization: Bearer $NATURAL_USER_TOKEN" \ -H "Idempotency-Key: $(uuidgen)" ``` # Overview Source: https://docs.natural.com/guides/concepts/agent-keys Credentials bound to a single agent An agent key (`ak_ntl_*`) is a credential bound to one [Agent](/guides/concepts/agents). A request made with it *is* that agent. Give it to an agent runtime; it carries the same verified binding as an agent-scoped [MCP OAuth](/guides/platform/mcp) grant. An [API key](/guides/concepts/api-keys) is the party-wide counterpart: it can attribute a call to an agent, but it isn't one. An agent key is scoped to its agent, nothing else. ## Issuing Agent keys can only be issued, rotated, or revoked from a user session, so a leaked party key can't mint agent identities. ## Rotation and revocation Rotating issues a replacement and keeps the old key valid for a grace period you choose, up to 24 hours, so a running agent switches over without downtime. Revoking invalidates a key immediately, with no replacement. Either way, the secret is shown once. # Overview Source: https://docs.natural.com/guides/concepts/agents Autonomous actors that can move money Developers create agents to run autonomous [Payments](/guides/concepts/payments) workflows. An agent can pay on behalf of its creator, or on behalf of its creator's customers who have connected a wallet to it. A developer can register many agents, and each agent can act for many parties. Every agent-customer pair carries its own permissions and limits: the developer requests them, the customer approves, and either side can update or revoke the relationship later through the dashboard or API. ## Agent customer model Kendall (Developer) invites his customer to one of his agents with specific permissions and limits. Eric (Customer) reviews and approves the agent-customer relationship: ```text theme={null} 1. Kendall (Developer) └─ Creates Agent (Natural Bot) └─ Adds Eric as a customer for the agent └─ Permissions: [payments.create, payments.read] └─ Limit: $1000 per transaction 2. Eric (Customer) └─ Approves the agent-customer relationship 3. Kendall (Developer) └─ Uses Natural Bot to pay Klaire (Contractor) on behalf of Eric ``` Each relationship carries the permissions the customer granted and a per-transaction limit. A payment over the limit isn't rejected; it holds as an [Approval](/guides/concepts/approvals) for the party's owners or admins to approve or deny. This invite-and-approve flow is **Natural Connect**. Start with [Invite a customer](/guides/connect/invite-customer). ## Agent authentication Agents authenticate via our [SDKs](/guides/platform/sdks) using credentials the developer owns. There are two ways to establish agent identity: * **Agent keys** (`ak_ntl_…`) — A credential bound to one agent. Requests resolve as that agent automatically. The same verified binding applies to agent-scoped [MCP OAuth](/guides/platform/mcp) grants. * **API keys** (`sk_ntl_…`) — A party credential for user/party actions. Prefer agent keys or agent-scoped OAuth for new agent integrations. See [Authentication](/api-reference/authentication) for the full credential model. Agent attribution is optional: dashboard users, user-scoped MCP grants, and plain API key calls move money as user/party actions without naming an agent. ### Instance ID required for agent money movement A money-movement request attributed to an agent (by agent key or agent-scoped OAuth grant) must also carry an `instance_id` (`X-Instance-ID`) for transaction observability. Agent-attributed money-movement requests without `X-Instance-ID` are rejected with a 400 error (`missing_instance_id`). ## Agent instances An `instance_id` groups related agent executions. Natural tracks every payment on its own, but an `instance_id` lets you tie the actions of one logical workflow together. You control the value: pass a stable string that identifies the run. # Overview Source: https://docs.natural.com/guides/concepts/api-keys Your party's server-side credential An API key (`sk_ntl_*`) is your [Party](/guides/concepts/parties)'s server-side credential. A request made with it acts as your party, limited to the scopes the key was created with. Keep it on servers you control, never in a browser or client app. ## Scopes Every key carries an explicit scope list, so you can create narrow keys for narrow jobs. A call outside the key's scopes is rejected. ## Keys and agents An API key on its own moves money as a party action. It can attribute a call to an agent, but it remains a party credential; a credential that *is* an agent is an [Agent key](/guides/concepts/agent-keys). See [Authentication](/api-reference/authentication) for the full credential model. ## Secrets The secret is returned once, on creation, and can never be read again. Revoking a key invalidates it immediately. # Overview Source: https://docs.natural.com/guides/concepts/approvals Payments and transfers held for review by a spending limit An approval (`apr_*`) is a payment or transfer held for a human decision. Natural creates one when a movement breaches a [Limit](/guides/concepts/limits): the original call still succeeds, but nothing moves until someone approves. You never create an approval; you resolve the ones the system opens. ## Who resolves an approval An approval belongs to the party behind the limit that was breached. You resolve holds triggered by your own party and agent limits. When a hold comes from a limit a customer set on their connection with you, that customer resolves it. If a single movement breaches limits owned by more than one party, it must be approved by each of those parties before the money moves. An agent can never clear a hold, by design. Approvals are resolved in the dashboard or with your own credentials, never by the agent that triggered them. # Overview Source: https://docs.natural.com/guides/concepts/customers Customers are parties who have authorized your agents to act for them A customer is a [Party](/guides/concepts/parties) who has authorized your [Agents](/guides/concepts/agents) to act for them. The customer resource is that relationship seen from your side: who connected, which of your agents they authorized, and what each agent may do. This relationship is [Natural Connect](/guides/products/connect). Start one by [inviting a customer](/guides/connect/invite-customer). A customer's `id` is their party ID (`pty_*`). There are two ways to connect a customer: invite a specific customer by email or phone, or create an invitation link that any customer can open. Both offer the same thing - a set of your agents, with the permissions and limits you set, for the customer to approve. ## Invitations Every customer relationship starts as an invitation. It names one or more agents, the permissions you're asking for, and optionally a per-transaction limit; the customer sees exactly what you asked for, and nothing moves until they accept. Pending invitations can be revoked, and accepted ones appear in your customer list. ## Invitation links An [invitation link](/guides/connect/invite-customer#share-an-invitation-link) is an invitation with no recipient. It carries the same offer, one or more agents with the permissions and limits you set, and any customer who opens it sees exactly that offer; nothing moves until they accept. The link's URL is returned once, when you create it. Revoking a link stops new connections and leaves existing ones untouched. Each connection records the link that created it, so one link per channel tells you where every customer came from. ## Acting for a customer Each agent-customer pair carries its own permissions and limits. Depending on the grant, an agent can send and request payments on the customer's behalf, and with `external_accounts.create` it can [link the customer's bank account](/guides/connect/link-customer-bank). ## Revoking the relationship Either side can revoke at any time, you through the API or dashboard and the customer from theirs. New activity stops immediately. # Overview Source: https://docs.natural.com/guides/concepts/events The record of what happened on your account An event is Natural's record that something happened on your account: each one names what occurred and carries a point-in-time snapshot of the resource it describes. Events are what [Webhooks](/guides/concepts/webhooks) deliver to your server; the endpoints below let you list and fetch the same events on demand. ## The event object The API returns events as standard resources. The `eventType` attribute names what happened, and the resource snapshot lives at `payload.object`: ```json theme={null} { "data": { "type": "event", "id": "evt_019cd3444a7a70efaf554fd8450d221a", "attributes": { "eventType": "wallet.created", "resourceId": "wal_019cd3444a7a70efaf554fd8450d334b", "resourceType": "wallet", "payload": { "object": { "partyId": "pty_019cd34e27bf78399b4e75b327d2ab25", "walletType": "standard", "status": "active", "displayName": "My Wallet", "currency": "usd" } }, "createdAt": "2026-01-15T14:30:00Z" }, "relationships": { "party": { "data": { "type": "party", "id": "pty_019cd34e27bf78399b4e75b327d2ab25" } } } } } ``` Webhook deliveries carry the same event in a flat body: there, `type` names the event and the snapshot lives at `data.object`. See [Webhooks](/guides/concepts/webhooks) for the delivery format, signature verification, and retries. The [Event catalog](/api-reference/event-catalog) documents every event type and the full snapshot payload it carries. # Overview Source: https://docs.natural.com/guides/concepts/external-accounts Bank accounts linked to a party for deposits and withdrawals An external account (`eac_*`) is a bank account linked to a party. It's the bridge between Natural [Wallets](/guides/concepts/wallets) and the banking system: the source of deposits, the destination of withdrawals, and a funding option when fulfilling a [Payment request](/guides/concepts/payment-requests). ## Linking Link your own bank account in the dashboard, where Natural connects it through Plaid. If you already run Plaid in your product, the API accepts a Plaid processor token instead, reusing the connection you already have. The same path lets an authorized agent [link a customer's bank account](/guides/connect/link-customer-bank) so the customer's wallet can be funded from their bank. ## Removing Removing an external account stops new deposits and withdrawals against it. # Overview Source: https://docs.natural.com/guides/concepts/limits Limits on how much money can move without extra approval Limits cap how much money can move without additional approval. This section manages your **account limits**, a setting on your [Party](/guides/concepts/parties) rather than a resource of its own: one cap per transaction, one per day, and one per month. Account limits are the backstop over everything you and your agents do. ## Holds, not blocks A limit never hard-blocks a payment. A movement that breaches a cap still succeeds as an API call, but no money moves: it holds as an [Approval](/guides/concepts/approvals) until someone approves or denies it. ## Three gates Account limits are one of three gates a movement can pass through. Agent limits live on the [Agent](/guides/concepts/agents) and hold one agent to a tighter budget; connection limits are set by your customer when they authorize an agent. Every applicable gate must clear, so in effect the tightest cap wins. See the [Limits guide](/guides/controls/limits) for how they compose. ## Who sets them You do, from a user session, an API key, or the dashboard. An agent can read the caps that gate it, so it can explain why a payment was held, but it can never set, raise, or remove them. # Overview Source: https://docs.natural.com/guides/concepts/parties The real-world identity behind your wallets and agents A party is Natural's representation of a real-world identity. It ties together your [Agents](/guides/concepts/agents), [Wallets](/guides/concepts/wallets), and the authorization you grant through [Natural Connect](/guides/connect/invite-customer). There are two types of party: Individuals and Businesses. Most developers sign up as a Business so they can invite teammates. Every party clears [Compliance](/guides/overview/compliance) (KYB for businesses, KYC for individuals). ## Handles Every party can claim a **handle**, the `@name` other parties use to pay or request money from you without knowing an email, phone number, or party ID. Agents get composed handles under their party (`@acme-support`), so a handle also names who an agent acts for. Claim or rename your handle in the dashboard, or with [`PUT /parties/me/handle`](/api-reference/parties/set-party-handle). Handles cannot be cleared: every party keeps one once claimed. Handle changes are reserved for signed-in users on a verified party; API keys and agents cannot change a handle. Renaming releases the previous name into a 14-day hold, during which only your party can take it back. ## Canonical examples Throughout our documentation, we use these three parties as canonical examples: ### Kendall Developer (Business Party) Kendall is an AI agent developer who has integrated Natural into his AI property management platform. He: * Uses agents to automate vendor and contractor payments * Uses Natural to allow his agents to pay his customers ### Eric Customer (Business Party) Eric runs a property management company and uses Kendall's agents. He: * Onboards as Kendall's customer through [Natural Connect](/guides/connect/invite-customer) and authorizes Kendall's agents to pay on his behalf * Uses Kendall's agents to pay vendors and contractors automatically ### Klaire Contractor (Individual Party) Klaire is a plumber who receives payments from businesses. She: * Is sent money by Kendall's agents on behalf of Eric * Signs up to claim her first payment from Eric # Overview Source: https://docs.natural.com/guides/concepts/party-invitations Invite teammates to join your business party A party invitation represents an invitation for someone to join your [Party](/guides/concepts/parties). It's how a business brings teammates into one shared Natural account: same wallets, same agents, each person under their own login. ## Roles and lifecycle Each invitation names an email and the role granted on acceptance: `ADMIN` or `MEMBER`. A party has exactly one owner, set when the party is created, so `OWNER` can't be granted by invitation. It stays pending until accepted, can be revoked while pending, and once accepted the person appears among your party's members. # Overview Source: https://docs.natural.com/guides/concepts/payment-requests Request, accept, and reconcile payment from another party A payment request (`prq_*`) is a request for another party to send you funds. [Payments](/guides/concepts/payments) move money out of your wallet; payment requests bring money in. You identify the payer by email, phone number, handle, or party or agent ID. Natural notifies the payer and, once they fulfill the request, deposits the funds into your wallet. ## Lifecycle A request stays open until someone acts on it: the payer fulfills or declines, or you cancel. Fulfillment is the moment money moves, from the payer's wallet or a verified linked bank account, and the movement lands in [Transactions](/guides/concepts/transactions). Requests you've been asked to pay appear in a separate incoming list, apart from the requests you created. ## Payment rails A payer can fulfill a request from their Natural wallet or over external payment rails: ACH, wire, RTP, and FedNow. For wires, Natural can issue one-time wire instructions and virtual account numbers that automatically reconcile the incoming funds to the original request, so you don't have to match payments by hand. ## Requesting for a customer An agent authorized by a customer can create requests that collect into the customer's wallet. The request belongs to the customer; your agent acts as the operator. ## Best practices Always give a full payer identifier (email, phone number, handle, or party or agent ID) so Natural can deliver the request. Specify amounts as integer cents (e.g. `2500` for \$25.00). All transactions are in U.S. Dollars for now. Write a clear description so the payer knows who is asking and what they're paying for. Include the invoice number and what it covers (e.g., "Invoice #12345 - Raw materials for Q1 2026"). The description is shown to the payer, and good detail also helps Natural flag anomalous transactions. A request stays open until the payer fulfills or declines it, or you cancel. If a payer hasn't acted, follow up with them, and cancel requests that are no longer valid so their payment links stop working. # Overview Source: https://docs.natural.com/guides/concepts/payments Send and receive money via direct transfers or payment claims You or your agent can send a payment two ways, and Natural picks the right one: a direct transfer when the recipient is already on Natural, or a payment claim when they aren't. Either way, the recipient can withdraw the funds to their bank account via ACH. Manage payments through the [SDKs](/guides/platform/sdks), [APIs](/api-reference/about), and [Dashboard](/guides/platform/dashboard). ## Direct transfer Send a payment by naming the recipient with their email, phone number, [handle](/guides/agents/handles), or party or agent ID. If that party is already on Natural, the money moves straight from the sender's wallet to theirs. If not, Natural sends a payment claim instead. ## Payment claims When the recipient isn't on Natural yet, Natural emails or texts them a claim link. They onboard, then claim the funds. Before they can withdraw, they must pass KYB/KYC under our [compliance](/guides/overview/compliance) program. A claim link stays valid until the recipient redeems it or the sender cancels the payment. ## Canceling a payment A payment that's still waiting to be claimed can be canceled. Until the recipient starts claiming, canceling voids the claim link and the money stays in your wallet, the real-world equivalent of voiding a check nobody has cashed yet. It's also how you handle a stale claim: cancel the payment and reissue it. The window closes the moment the recipient begins claiming; after that, the funds are theirs to collect. A direct transfer can't be canceled at all: the money lands in the recipient's wallet the moment you send it, so there's nothing in flight to take back. ## Best practices Always give a full recipient identifier (email, phone number, handle, or party or agent ID) so Natural can route the payment. Specify amounts as integer cents (e.g. `12345` for \$123.45). All transactions are in U.S. Dollars for now. Write clear descriptions so recipients know who paid them and why. Include the invoice number and what it covers (e.g., "Invoice #12345 - Raw materials for Q1 2026"). These show up in the email and SMS copies and in the product, and good detail also helps Natural flag anomalous transactions. If a recipient hasn't redeemed a claim, follow up with them or work with your customer on whether to reissue the payment with a fresh claim link. # Overview Source: https://docs.natural.com/guides/concepts/transactions The single ledger of every money movement on your account A transaction (`txn_*`) is one money movement recorded from your party's point of view. Every [Payment](/guides/concepts/payments) and [Transfer](/guides/concepts/transfers) that touches your party produces one, so the transaction list is the single feed to reconcile against. Transactions are read-only: you create payments and transfers. ## Reading a transaction Each transaction says what kind of movement it was, which direction the money went relative to you, and which payment or transfer produced it. Movements that settle asynchronously, like ACH deposits, carry a projected funds-available time. ## Internal transfers A transfer between two of your own wallets is one movement with two legs. It appears once in the party-wide list; filter by wallet to see that wallet's leg. # Overview Source: https://docs.natural.com/guides/concepts/transfers Move money between your wallets and your linked bank accounts A transfer (`trf_*`) moves money within your own account: between your [Wallets](/guides/concepts/wallets), or between a wallet and a linked [External account](/guides/concepts/external-accounts). ## Three kinds * **Deposits** pull funds from a linked bank account into a wallet. * **Withdrawals** push funds from a wallet back out to a linked bank account. * **Internal transfers** move funds between two of your own wallets. ## Settlement Deposits and withdrawals run over ACH and settle asynchronously. Internal transfers land instantly. ## Moving money for customers Transfers always move money within your own account. To move money for a [customer](/guides/concepts/customers), an authorized agent sends or requests payments on their behalf instead. See [Move money for a customer](/guides/connect/move-money-for-customer). # Overview Source: https://docs.natural.com/guides/concepts/wallet-access Which of your wallets each agent can move money from Wallet access is the attachment between one of your [Agents](/guides/concepts/agents) and one of your [Wallets](/guides/concepts/wallets). An agent carries no wallet access until you attach it, and access is granted per wallet, so you decide which balances each agent can touch. It isn't a standalone resource: an attachment is addressed by its wallet-agent pair. ## Attachments Attaching an agent lets it move money from that wallet; detaching takes that away. One attached wallet can be the agent's default, the one it spends from when a request doesn't name a wallet. Vault wallets refuse attachment, which keeps vault funds out of agents' reach. ## Who manages access You do, from a user session or an API key on your own party; an agent cannot attach itself or a sibling. To let an agent operate a customer's wallet, the customer grants that through a [customer invitation](/guides/concepts/customers). # Overview Source: https://docs.natural.com/guides/concepts/wallets Where a party holds and moves its funds A wallet holds a [Party](/guides/concepts/parties)'s funds. Every party gets one wallet automatically once onboarding completes, and money moves in and out over ACH, wire, and more. You can open more than one wallet (keeping payroll apart from operating cash, or giving an agent its own) and set which wallet each agent uses by default. See [Wallets](/guides/wallets/wallets) and [Wallet access](/guides/controls/wallet-access). ## Escrow When you pay a recipient who hasn't onboarded yet (by email or phone), the funds sit in escrow until they claim them. The sender's wallet is debited right away, but the recipient can't withdraw until they fully onboard. # Overview Source: https://docs.natural.com/guides/concepts/webhooks Get notified when things happen on your account A webhook is a URL you register so Natural can push [Events](/guides/concepts/events) to your server as they happen, instead of you polling for changes. When a payment completes, a deposit lands, or a customer connects, Natural sends the event to your URL. Each webhook subscribes to the event types you choose, every delivery is signed with the webhook's secret so you can verify it came from Natural, and failed deliveries are retried. Natural signs every delivery with the [Standard Webhooks](https://www.standardwebhooks.com) spec; verify it before trusting the payload. See the [Event catalog](/api-reference/event-catalog) for every event type and its full payload. ## 1. Register an endpoint [`POST /webhooks`](/api-reference/webhooks/create-webhook) registers your URL and subscribes it to event types. The signing secret is returned **once** in the response; store it immediately. ```python Python theme={null} import httpx, uuid response = httpx.post( "https://api.natural.com/webhooks", headers={ "Authorization": f"Bearer {api_key}", "Idempotency-Key": str(uuid.uuid4()), }, json={ "data": { "attributes": { "url": "https://yourdomain.com/hooks/natural", "enabledEvents": ["wallet.created", "party.updated"], } } }, ) webhook = response.json()["data"] signing_secret = webhook["attributes"]["signingSecret"] # shown once, store securely ``` ```typescript TypeScript theme={null} const response = await fetch("https://api.natural.com/webhooks", { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Idempotency-Key": crypto.randomUUID(), "Content-Type": "application/json", }, body: JSON.stringify({ data: { attributes: { url: "https://yourdomain.com/hooks/natural", enabledEvents: ["wallet.created", "party.updated"], }, }, }), }); const webhook = (await response.json()).data; const signingSecret = webhook.attributes.signingSecret; // shown once, store securely ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/webhooks \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "url": "https://yourdomain.com/hooks/natural", "description": "Production webhook", "enabledEvents": ["wallet.created", "party.updated"], "tags": { "env": "prod" } } } }' ``` `enabledEvents` accepts any event `type` from the [Event catalog](/api-reference/event-catalog), or the wildcard `"*"` (which must be the only entry). `description` and `tags` are optional. The `signingSecret` (`whsec_...`) appears only in this response. If you lose it, rotate it with [`POST /webhooks/{webhookId} /rotate-secret`](/api-reference/webhooks/rotate-webhook-signing-secret). ## 2. Verify the signature Every delivery is a POST whose JSON body is the [event object](/guides/concepts/events). Three signature headers ride on each delivery (lowercase, per the Standard Webhooks spec): | Header | Description | | ------------------- | --------------------------------------------------------------------- | | `webhook-id` | The event ID (`evt_...`), stable across retries. Use for idempotency. | | `webhook-timestamp` | Unix epoch seconds when the delivery was signed. | | `webhook-signature` | One or more space-separated `v1,` signatures. | Always verify before trusting a payload. Use the official [`standardwebhooks`](https://www.standardwebhooks.com) library (Python and Node); construct it with your `whsec_` secret and pass the raw body plus headers. `verify` returns the parsed event, or raises on a bad signature: ```python Python (FastAPI) theme={null} import os from fastapi import FastAPI, Request, HTTPException, BackgroundTasks from standardwebhooks import Webhook app = FastAPI() wh = Webhook(os.environ["NATURAL_WEBHOOK_SECRET"]) # the whsec_... value @app.post("/hooks/natural") async def handle(request: Request, background_tasks: BackgroundTasks): body = await request.body() # raw bytes, required for verification try: event = wh.verify(body, dict(request.headers)) except Exception: raise HTTPException(status_code=401, detail="Invalid signature") # Hand off async work and return 2xx fast. background_tasks.add_task(process_event, event, request.headers["webhook-id"]) return {"received": True} ``` ```typescript TypeScript (Express) theme={null} import express from "express"; import { Webhook } from "standardwebhooks"; const app = express(); const wh = new Webhook(process.env.NATURAL_WEBHOOK_SECRET!); // the whsec_... value // express.raw keeps the body as exact bytes for verification. app.post("/hooks/natural", express.raw({ type: "application/json" }), (req, res) => { let event; try { event = wh.verify(req.body, req.headers as Record); } catch { return res.status(401).send("Invalid signature"); } // Hand off async work and return 2xx fast. void processEvent(event, req.headers["webhook-id"]); res.json({ received: true }); }); ``` Verify against the **raw request body**, the exact bytes Natural sent. Parsing and re-serializing the JSON first changes the bytes and breaks verification. The library handles two details for you: it rejects deliveries whose `webhook-timestamp` is outside a tolerance window (replay defense), and it accepts a delivery if **any** of the space-separated signatures verifies, which is what lets a secret rotation overlap two valid secrets. The signed content is the string `{webhook-id}.{webhook-timestamp}.{body}`. Strip the `whsec_` prefix from the secret, base64-decode the remainder for the HMAC key, compute HMAC-SHA256, and base64-encode the result. Compare it against each space-separated `v1,` entry. ```python Python theme={null} import base64, hashlib, hmac def verify(body: bytes, headers: dict, signing_secret: str) -> bool: key = base64.b64decode(signing_secret.removeprefix("whsec_")) signed = f"{headers['webhook-id']}.{headers['webhook-timestamp']}.{body.decode()}" expected = base64.b64encode(hmac.new(key, signed.encode(), hashlib.sha256).digest()).decode() for entry in headers["webhook-signature"].split(" "): version, _, sig = entry.partition(",") if version == "v1" and hmac.compare_digest(sig, expected): return True return False ``` ```typescript TypeScript theme={null} import { createHmac, timingSafeEqual } from "crypto"; function verify(body: string, headers: Record, signingSecret: string): boolean { const key = Buffer.from(signingSecret.replace(/^whsec_/, ""), "base64"); const signed = `${headers["webhook-id"]}.${headers["webhook-timestamp"]}.${body}`; const expected = Buffer.from(createHmac("sha256", key).update(signed).digest("base64")); return headers["webhook-signature"].split(" ").some((entry) => { const [version, sig] = entry.split(","); if (version !== "v1" || !sig) return false; const sigBuf = Buffer.from(sig); return sigBuf.length === expected.length && timingSafeEqual(sigBuf, expected); }); } ``` ## 3. Delivery, retries & failures Natural treats any **2xx** response as success. The delivery request times out after **30 seconds**. Failed deliveries (non-2xx, network error, or timeout) are retried up to **7 attempts** total with jittered backoff (\~20% jitter): | Attempt | Delay after previous | | ------- | -------------------- | | 1 | immediate | | 2 | 5 seconds | | 3 | 5 minutes | | 4 | 30 minutes | | 5 | 2 hours | | 6 | 8 hours | | 7 | 12 hours | An endpoint that fails every attempt for 5 consecutive events is automatically disabled. Re-enable it with [`PATCH /webhooks/{webhookId}`](/api-reference/webhooks/update-webhook) once your endpoint is healthy. ## 4. Redeliver events The `webhook-id` header contains the event ID (`evt_...`). You can also find events with `GET /events` and `GET /events/{eventId}`. You can redeliver an event to a webhook for up to 90 days after the event was created, provided that webhook was one of the event's original destinations. The endpoint must be enabled, and another manual redelivery for the same event and webhook cannot already be in progress. For a delegated event, the exact delegation that authorized the original delivery must still be active and grant access to that event type. Revoking it permanently closes redelivery for that event and webhook; creating a new delegation does not reopen it. `POST /webhooks/{webhookId}/events/{eventId}/redeliver` sends the stored event once and returns `202 Accepted` with the pending redelivery. It takes no request body and requires an `Idempotency-Key` header. A redelivery keeps the original event payload and `webhook-id`, but uses the webhook's current URL and active signing secret with a fresh `webhook-timestamp` and signature. Treat it as a possible duplicate, and do not assume it will arrive in order relative to other events. Automatic retries continue independently, even when the manual redelivery succeeds. The automatic and manual sends can overlap, and another copy can arrive later. A manual failure does not contribute to automatic endpoint disabling. A delegated redelivery is sent only to the same developer webhook that received the original delivery. It does not fan out to the customer's webhooks or any other endpoint. Natural checks the original delegation again immediately before transmission. If it is no longer valid, the redelivery is marked `CANCELED` and no request is sent. If authorization is temporarily unavailable, Natural retries that check for a bounded period without consuming the manual attempt; no request is sent unless authorization succeeds. ## Best practices * **Return 2xx fast.** Acknowledge as soon as the signature verifies, then process the event asynchronously; slow handlers risk the 30-second timeout and trigger retries. * **Deduplicate on `webhook-id`.** Retries reuse the same `webhook-id`, and Natural may deliver an event more than once; record processed IDs and skip duplicates. * **Store the `whsec_` secret in a secrets manager**, never in source control. Rotate it with [`POST /webhooks/{webhookId}/rotate-secret`](/api-reference/webhooks/rotate-webhook-signing-secret); the previous secret stays valid for the grace period you specify, so both signatures arrive during the overlap. * **Don't depend on event ordering.** Retries and independent delivery mean events can arrive out of order; use the `version` field on the resource snapshot, or refetch the resource, when ordering matters. # Invite a customer Source: https://docs.natural.com/guides/connect/invite-customer Invite a specific customer or share an invitation link to connect your agents There are two ways to ask a customer to approve your agents: a customer invitation, sent to one person by email or phone, or an [invitation link](#share-an-invitation-link), an open-ended shareable invitation that can connect many customers. Either way, the customer chooses what to allow and which wallet the agent can use, and nothing moves until they accept. ## Send the invitation Invite your customer over email with [`POST /customers/invitations`](/api-reference/customers/invite-customers). Natural handles the customer communication. In Sandbox, use [`POST /simulations/invite-customer`](/api-reference/simulations/create-test-customer) when you want Natural to create a hidden synthetic customer and you only want to choose agents. ```python Python theme={null} from naturalpay import Natural client = Natural() invitations = client.customers.create_invitations( recipients=[{"type": "email", "value": "customer@example.com"}], agents=[ { "agentId": "agt_019cd1798d637a4da75dce386343931d", "permissions": [ "payments.read", "payments.create", "external_accounts.create", "wallets.read", "wallets.update", "party.read", "party.update", ], "limits": {"perTransaction": 100_000}, } ], ) for invitation in invitations.data: print(invitation.id, invitation.attributes.url) ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const invitations = await client.customers.createInvitations({ recipients: [{ type: "email", value: "customer@example.com" }], agents: [ { agentId: "agt_019cd1798d637a4da75dce386343931d", permissions: [ "payments.read", "payments.create", "external_accounts.create", "wallets.read", "wallets.update", "party.read", "party.update", ], limits: { perTransaction: 100_000 }, }, ], }); for (const invitation of invitations.data) { console.log(invitation.id, invitation.attributes.url); } ``` ```bash CLI theme={null} natural customers createInvitations --json '{ "recipients": [{ "type": "email", "value": "customer@example.com" }], "agents": [{ "agentId": "agt_019cd1798d637a4da75dce386343931d", "permissions": ["payments.read", "payments.create", "external_accounts.create", "wallets.read", "wallets.update", "party.read", "party.update"], "limits": { "perTransaction": 100000 } }] }' ``` ```text MCP theme={null} Invite customer@example.com to connect my Procurement Agent. Let it run money end to end for them, limited to $1,000 per transaction. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/customers/invitations \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "recipients": [{ "type": "email", "value": "customer@example.com" }], "agents": [{ "agentId": "agt_019cd1798d637a4da75dce386343931d", "permissions": ["payments.read", "payments.create", "external_accounts.create", "wallets.read", "wallets.update", "party.read", "party.update"], "limits": { "perTransaction": 100000 } }] } } }' ``` Each invitation in the response carries `attributes.url`, the same `/connect` link Natural emails the recipient. Surface it in your own product or send it through your own channel when you [own the customer communication](/guides/connect/own-customer-communications); either path lands on the same acceptance page. ## Share an invitation link An invitation link is the same offer with no recipient: create it once, share the URL anywhere, and any customer who opens it can accept. Create one with [`POST /customers/invitation-links`](/api-reference/invitation-links/create-invitation-link), naming the agents it offers and what each one asks for. The create response is the only place the API returns the shareable `url`, so store it then. `expiresAt` is optional; a link without one stays open until you revoke it. ```python Python theme={null} from naturalpay import Natural client = Natural() link = client.invitation_links.create( name="Q3 carrier onboarding", proposed_agents=[ { "agentId": "agt_019cd1798d637a4da75dce386343931d", "permissions": ["payments.read", "payments.create"], "limits": {"perTransaction": 100_000}, } ], ) print(link.data.attributes.url) ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const link = await client.invitationLinks.create({ name: "Q3 carrier onboarding", proposedAgents: [ { agentId: "agt_019cd1798d637a4da75dce386343931d", permissions: ["payments.read", "payments.create"], limits: { perTransaction: 100_000 }, }, ], }); console.log(link.data.attributes.url); ``` ```bash CLI theme={null} natural invitationLinks create --json '{ "name": "Q3 carrier onboarding", "proposedAgents": [{ "agentId": "agt_019cd1798d637a4da75dce386343931d", "permissions": ["payments.read", "payments.create"], "limits": { "perTransaction": 100000 } }] }' ``` ```text MCP theme={null} Create an invitation link named "Q3 carrier onboarding" offering my Procurement Agent with payments access, limited to $1,000 per transaction. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/customers/invitation-links \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "name": "Q3 carrier onboarding", "proposedAgents": [{ "agentId": "agt_019cd1798d637a4da75dce386343931d", "permissions": ["payments.read", "payments.create"], "limits": { "perTransaction": 100000 } }] } } }' ``` [List](/api-reference/invitation-links/list-invitation-links) and [get](/api-reference/invitation-links/get-invitation-link) return each link's offer and status with a masked token and no URL. If you lose a link's URL, revoke the link and create a new one. [Revoke](/api-reference/invitation-links/revoke-invitation-link) a link to close the offer. It stops accepting new customers immediately; customers it already connected are untouched, because a link only ever offers, it never grants. Every connection made through a link records its source: the `delegation.activated` webhook carries `sourceType: "link"` and `sourceId` set to the link's ID. Run one link per channel and count acceptances by `sourceId` to measure per-channel conversion. ## What the customer does They see who is requesting and which agents will act. A revoked or expired invitation link shows only that it is no longer available. A customer new to Natural creates an account and verifies as part of accepting. Natural handles the compliance. They choose which wallet the agent may use. The invitation flips to `ACCEPTED` and the customer shows up in [your customers](/api-reference/customers/list-customers). An invitation link keeps working for the next customer; one link can onboard any number of them. Their `id` is the party ID you pass to act as them. From there the agent moves money for them within the permissions and limit they granted. # Link a customer's bank account Source: https://docs.natural.com/guides/connect/link-customer-bank Link a customer's bank account so their Natural wallet can be funded from their bank Reuse your own Plaid connection to link a customer's bank account to Natural, so their wallet can be funded from their bank. This guide is only relevant if you have a Plaid account for your platform. ## Get a processor token If you already use Plaid, reuse the customer's existing connection instead of Natural asking them to link their bank again. Run your existing Plaid Link flow for the customer's institution. On success, Plaid gives you a `public_token`. Exchange the `public_token` for your own Plaid `access_token`, then call Plaid [`/processor/token/create`](https://plaid.com/docs/api/processors/#processortokencreate) for the selected `account_id` with `processor: "natural"`. Plaid returns a `processor_token` scoped to that one account. You keep the Plaid item and access token; Natural only receives the processor token. Pass the `processor_token` to [`POST /external-accounts/processor-token`](/api-reference/external-accounts/link-external-account) with the customer's `partyId`. The full Plaid walkthrough lives in [Link a bank account](/guides/transfers/deposit-and-withdraw). Nothing about obtaining the token changes when the account belongs to a customer: the only difference is that `partyId` is the customer's party. ## Link the account Call [`POST /external-accounts/processor-token`](/api-reference/external-accounts/link-external-account) with the customer's `partyId` and `processorToken`. ```python Python theme={null} import uuid from naturalpay import Natural # Acts as the agent that holds external_accounts.create on the customer. agent_client = Natural( x_agent_id="agt_019cd1798d637a4da75dce386343931d", x_instance_id=str(uuid.uuid4()), ) accounts = agent_client.external_accounts.create_from_processor_token( party_id="pty_019cd1798d617f65a79cb965dda9eac3", processor_token="processor-sandbox-abc123", institution_name="Chase", idempotency_key=str(uuid.uuid4()), ) print(accounts.data[0].id) ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const accounts = await client.externalAccounts.createFromProcessorToken( { partyId: "pty_019cd1798d617f65a79cb965dda9eac3", processorToken: "processor-sandbox-abc123", institutionName: "Chase", idempotencyKey: crypto.randomUUID(), }, { xAgentId: "agt_019cd1798d637a4da75dce386343931d", xInstanceId: crypto.randomUUID(), }, ); console.log(accounts.data[0].id); ``` ```bash CLI theme={null} natural external-accounts createFromProcessorToken \ --party-id pty_019cd1798d617f65a79cb965dda9eac3 \ --processor-token processor-sandbox-abc123 \ --institution-name Chase \ --x-agent-id agt_019cd1798d637a4da75dce386343931d \ --x-instance-id "$(uuidgen)" \ --idempotency-key "$(uuidgen)" ``` ```text MCP theme={null} Using my Procurement Agent, link this Plaid processor token as a bank account for customer pty_019cd1798d617f65a79cb965dda9eac3: processor-sandbox-abc123 (Chase). ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/external-accounts/processor-token \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "X-Agent-ID: agt_019cd1798d637a4da75dce386343931d" \ -H "X-Instance-ID: $(uuidgen)" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "partyId": "pty_019cd1798d617f65a79cb965dda9eac3", "processorToken": "processor-sandbox-abc123", "institutionName": "Chase" } } }' ``` ## Keep track of the account Store the `eac_*` id from the link response for your own records and account picker. [`GET /external-accounts`](/api-reference/external-accounts/list-external-accounts) lists the accounts linked to **your own** party, so it will not show a customer's accounts; the link response is where you capture theirs. With the account linked, the customer's wallet can be funded from their bank, and your agent can [move money on their behalf](/guides/connect/move-money-for-customer). # Manage customer access Source: https://docs.natural.com/guides/connect/manage-access Manage agents across your connected customers See which customers your agents act for, inspect what each one granted, and revoke an agent or a pending invitation whenever you need to. ## See who your agents act for Your customers list holds every customer who has connected an agent to you. Each entry's `id` is the party ID you pass as `customerPartyId` when you act for them, and each entry shows the agents acting for that customer, with the permissions and `perTransaction` limit in force. Read it with [`GET /customers`](/api-reference/customers/list-customers). ```python Python theme={null} from naturalpay import Natural client = Natural() customers = client.customers.list() for customer in customers.data: print(customer.id, customer.attributes.name) ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const customers = await client.customers.list(); for (const customer of customers.data) { console.log(customer.id, customer.attributes.name); } ``` ```bash CLI theme={null} natural customers list ``` ```text MCP theme={null} List my active customers and the agents acting for them. ``` ```bash cURL theme={null} curl https://api.natural.com/customers \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` Filter by status: `active` for accepted access, `pending` for unaccepted invitations, or `all` for both. A pending invitation stays on your invitations list until the customer accepts. ## Inspect one customer Read a single customer with [`GET /customers/{customerId}`](/api-reference/customers/get-customer) to see exactly which agents, permissions, and limits are in force before you change anything. The `customerId` is that customer's party ID. ```python Python theme={null} customer = client.customers.get("pty_019cd1798d617f65a79cb965dda9eac3") print(customer.data.id, customer.data.attributes.agents) ``` ```typescript TypeScript theme={null} const customer = await client.customers.get({ customerId: "pty_019cd1798d617f65a79cb965dda9eac3", }); console.log(customer.data.id, customer.data.attributes.agents); ``` ```bash CLI theme={null} natural customers get --customer-id pty_019cd1798d617f65a79cb965dda9eac3 ``` ```text MCP theme={null} Show me customer pty_019cd1798d617f65a79cb965dda9eac3 and what my agents can do for them. ``` ```bash cURL theme={null} curl https://api.natural.com/customers/pty_019cd1798d617f65a79cb965dda9eac3 \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` ## See pending invitations Invitations you have sent but nobody has accepted yet live on a separate list. Read it with [`GET /customers/invitations`](/api-reference/customers/list-customer-invitations) to confirm what is still open before you revoke. ```python Python theme={null} pending = client.customers.list_invitations() for invitation in pending.data: print(invitation.attributes.email, invitation.attributes.status) ``` ```typescript TypeScript theme={null} const pending = await client.customers.listInvitations(); for (const invitation of pending.data) { console.log(invitation.attributes.email, invitation.attributes.status); } ``` ```bash CLI theme={null} natural customers listInvitations ``` ```text MCP theme={null} Show my pending customer invitations. ``` ```bash cURL theme={null} curl https://api.natural.com/customers/invitations \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` Each recipient groups its `agentInvitations`, one `adi_*` per agent you invited them to. You revoke by that `invitationId`. ## Revoke a pending invitation Cancel an invitation the recipient has not accepted with [`DELETE /customers/invitations/{invitationId}`](/api-reference/customers/revoke-customer-invitation), and they can no longer accept it. ```python Python theme={null} client.customers.revoke_invitation("adi_550e8400e29b41d4a716446655440000") ``` ```typescript TypeScript theme={null} await client.customers.revokeInvitation({ invitationId: "adi_550e8400e29b41d4a716446655440000", }); ``` ```bash CLI theme={null} natural customers revokeInvitation --invitation-id adi_550e8400e29b41d4a716446655440000 ``` ```bash cURL theme={null} curl -X DELETE https://api.natural.com/customers/invitations/adi_550e8400e29b41d4a716446655440000 \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` The invitation's `status` becomes `CANCELED` with a `cancelReason` of `DEVELOPER_REVOKED`. Revoking an already-canceled or already-accepted invitation is a no-op, so retries are safe. ## Revoke an agent's access Once a customer has accepted, you revoke per agent. [`DELETE /customers/{customerId}/agents/{agentId}`](/api-reference/customers/revoke-agent-access) strips one agent's authority over that customer immediately. `customerId` is the customer's party ID, and `agentId` is the agent you are pulling. ```python Python theme={null} client.customers.revoke_agent( "pty_019cd1798d617f65a79cb965dda9eac3", "agt_019cd1798d637a4da75dce386343931d", ) ``` ```typescript TypeScript theme={null} await client.customers.revokeAgent({ customerId: "pty_019cd1798d617f65a79cb965dda9eac3", agentId: "agt_019cd1798d637a4da75dce386343931d", }); ``` ```bash CLI theme={null} natural customers revokeAgent \ --customer-id pty_019cd1798d617f65a79cb965dda9eac3 \ --agent-id agt_019cd1798d637a4da75dce386343931d ``` ```bash cURL theme={null} curl -X DELETE https://api.natural.com/customers/pty_019cd1798d617f65a79cb965dda9eac3/agents/agt_019cd1798d637a4da75dce386343931d \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` If the agent you remove is the last one acting for that customer, the whole customer resource comes back with `meta.deleted: true`, and the relationship is over. Revocation is idempotent: pulling an agent that is already gone changes nothing, so it is safe to retry. # Move money for a customer Source: https://docs.natural.com/guides/connect/move-money-for-customer Pay and request on a customer's behalf. Once a customer connects their agent to you, one field, `customerPartyId`, switches a payment or request from your money to theirs. Everything else works exactly like moving your own money. Every action here needs the customer to have [connected their agent](/guides/connect/invite-customer). Find a customer's `customerPartyId` by listing your customers. Each customer's `id` is the party id you pass. ```python Python theme={null} customers = client.customers.list() for customer in customers.data: print(customer.id, customer.attributes.name) ``` ```typescript TypeScript theme={null} const customers = await client.customers.list(); for (const customer of customers.data) { console.log(customer.id, customer.attributes.name); } ``` ```bash CLI theme={null} natural customers list ``` ```text MCP theme={null} List my active customers and their party IDs. ``` ```bash cURL theme={null} curl https://api.natural.com/customers \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` ## Pay from the customer's wallet Set `customerPartyId` on [`POST /payments`](/api-reference/payments/create-payment) to the customer's party id and the payment draws from **their** wallet, within the permissions and limits they granted. Everything else works like [Send a payment](/guides/payments/send-payment): the same counterparty types, the same claim link for new recipients, the same status flow. Omit `customerPartyId` and the payment comes from your own wallet instead. ```python Python theme={null} import uuid from naturalpay import Natural agent_client = Natural( x_agent_id="agt_019cd1798d637a4da75dce386343931d", x_instance_id=str(uuid.uuid4()), ) payment = agent_client.payments.create( amount=500_000, currency="USD", counterparty={"type": "party_id", "value": "pty_019cd1798d627ad9bc302511c4f2c115"}, customer_party_id="pty_019cd1798d617f65a79cb965dda9eac3", description="Q4 2025 development work", idempotency_key=str(uuid.uuid4()), ) ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const payment = await client.payments.create( { amount: 500_000, currency: "USD", counterparty: { type: "party_id", value: "pty_019cd1798d627ad9bc302511c4f2c115" }, customerPartyId: "pty_019cd1798d617f65a79cb965dda9eac3", description: "Q4 2025 development work", idempotencyKey: crypto.randomUUID(), }, { xAgentId: "agt_019cd1798d637a4da75dce386343931d", xInstanceId: crypto.randomUUID(), }, ); ``` ```bash CLI theme={null} natural payments create \ --amount 500000 \ --currency USD \ --params '{"counterparty": {"type": "party_id", "value": "pty_019cd1798d627ad9bc302511c4f2c115"}}' \ --customer-party-id pty_019cd1798d617f65a79cb965dda9eac3 \ --description "Q4 2025 development work" \ --x-agent-id agt_019cd1798d637a4da75dce386343931d \ --x-instance-id "$(uuidgen)" \ --idempotency-key "$(uuidgen)" ``` ```text MCP theme={null} Using my Procurement Agent, pay @natural-contractor $5,000 from customer@example.com's wallet for Q4 development work. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/payments \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "X-Agent-ID: agt_019cd1798d637a4da75dce386343931d" \ -H "X-Instance-ID: $(uuidgen)" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "amount": 500000, "currency": "USD", "counterparty": { "type": "party_id", "value": "pty_019cd1798d627ad9bc302511c4f2c115" }, "customerPartyId": "pty_019cd1798d617f65a79cb965dda9eac3", "description": "Q4 2025 development work" } } }' ``` The response carries a `pay_*` id and an initial `status`, and its `sender` is the customer's party, confirming the funds came from their wallet. ## Request into the customer's wallet Collecting for a customer is [Request a payment](/guides/payments/request-payment) with the same one field. Set `customerPartyId` on [`POST /payment-requests`](/api-reference/paymentrequests/create-payment-request) to the customer's party and the funds land in **their** default receiving wallet when the payer settles. Natural delivers the request to the payer automatically, on whatever channel matches how you addressed them. ```python Python theme={null} import uuid from naturalpay import Natural agent_client = Natural( x_agent_id="agt_019cd1798d637a4da75dce386343931d", x_instance_id=str(uuid.uuid4()), ) request = agent_client.payment_requests.create( amount=2500, currency="USD", description="Invoice 7", payer_name="Natural Client", payer={"type": "email", "value": "client@example.com"}, customer_party_id="pty_019cd1798d617f65a79cb965dda9eac3", idempotency_key=str(uuid.uuid4()), ) ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const request = await client.paymentRequests.create( { amount: 2500, currency: "USD", description: "Invoice 7", payerName: "Natural Client", payer: { type: "email", value: "client@example.com" }, customerPartyId: "pty_019cd1798d617f65a79cb965dda9eac3", idempotencyKey: crypto.randomUUID(), }, { xAgentId: "agt_019cd1798d637a4da75dce386343931d", xInstanceId: crypto.randomUUID(), }, ); ``` ```bash CLI theme={null} natural payment-requests create \ --amount 2500 \ --currency USD \ --description "Invoice 7" \ --payer-name "Natural Client" \ --params '{"payer": {"type": "email", "value": "client@example.com"}}' \ --customer-party-id pty_019cd1798d617f65a79cb965dda9eac3 \ --x-agent-id agt_019cd1798d637a4da75dce386343931d \ --x-instance-id "$(uuidgen)" \ --idempotency-key "$(uuidgen)" ``` ```text MCP theme={null} Using my Procurement Agent, request $25 from @natural-client for Invoice 7 and collect it into customer@example.com's wallet. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/payment-requests \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "X-Agent-ID: agt_019cd1798d637a4da75dce386343931d" \ -H "X-Instance-ID: $(uuidgen)" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "amount": 2500, "currency": "USD", "description": "Invoice 7", "payerName": "Natural Client", "payer": { "type": "email", "value": "client@example.com" }, "customerPartyId": "pty_019cd1798d617f65a79cb965dda9eac3" } } }' ``` The response's `requesterParty` is the customer, and when the payer settles the money lands in the customer's default receiving wallet, not yours. # Own customer communications Source: https://docs.natural.com/guides/connect/own-customer-communications Deliver the message to your customer and their counterparties yourself. By default, Natural emails customers about invitations, payment requests, and payment claims. Use `disableNotifications` to silence these emails and receive the relevant link in the API response instead, so you can deliver it through your own product. ## Suppress and deliver the link Pass `disableNotifications: ["recipient"]` on the create call, then send the returned link as desired through your own communication channels. Omit the field, or send an empty list, and Natural notifies as usual. ```python Python theme={null} from naturalpay import Natural client = Natural() request = client.payment_requests.create( customer_party_id="pty_019cd1798d617f65a79cb965dda9eac3", amount=25_000, currency="USD", payer={"type": "email", "value": "client@example.com"}, description="Design retainer", disable_notifications=["recipient"], ) print(request.data.attributes.payment_link_url) ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const request = await client.paymentRequests.create({ customerPartyId: "pty_019cd1798d617f65a79cb965dda9eac3", amount: 25_000, currency: "USD", payer: { type: "email", value: "client@example.com" }, description: "Design retainer", disableNotifications: ["recipient"], }); console.log(request.data.attributes.paymentLinkUrl); ``` ```bash CLI theme={null} natural paymentRequests create --json '{ "customerPartyId": "pty_019cd1798d617f65a79cb965dda9eac3", "amount": 25000, "currency": "USD", "payer": { "type": "email", "value": "client@example.com" }, "description": "Design retainer", "disableNotifications": ["recipient"] }' ``` ```text MCP theme={null} Request $250 from client@example.com for my customer Acme, and don't send Natural's own email — I'll deliver the link myself. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/payment-requests \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "data": { "attributes": { "customerPartyId": "pty_019cd1798d617f65a79cb965dda9eac3", "amount": 25000, "currency": "USD", "payer": { "type": "email", "value": "client@example.com" }, "description": "Design retainer", "disableNotifications": ["recipient"] } } }' ``` ## Where it applies Three calls accept the field, and each returns the link you need. | Call | What goes quiet | What you deliver | | --------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------- | | [`POST /payment-requests`](/api-reference/paymentrequests/create-payment-request) | the message to the payer | `paymentLinkUrl` | | [`POST /payments`](/api-reference/payments/create-payment) | the message to the recipient | `claimLink`, when the recipient is new to Natural | | [`POST /customers/invitations`](/api-reference/customers/invite-customers) | the invitation message | `url` | ## Acting on behalf of a customer For payments and payment requests, include the `customerPartyId` of the customer on whose behalf you are making the request. This confirms that your platform is authorized to control the recipient notification for that transaction. Customer invitations work differently. The invited customer has not yet authorized your agent, so you create the invitation directly without acting on the customer's behalf. ## What Natural still sends `"recipient"` silences only the message going to the counterparty on the call being made. The initiating party still gets notifications. Natural continues to send required account, identity, compliance, security, and transaction communications. It does not change customer onboarding, verification, agreements, or approval of requested agent access. # Approvals Source: https://docs.natural.com/guides/controls/approvals Approve or deny a payment held for review When a payment breaches a limit whose action is **hold**, Natural does not reject it. Natural holds the payment and opens an approval process. The call that sent the payment still returns `2xx`, and no money moves until someone approves. ## List what is held [`GET /approvals`](/api-reference/approvals/list-approvals) lists what is held, showing pending holds by default. ```python Python theme={null} approvals = client.approvals.list(status="pending") ``` ```typescript TypeScript theme={null} const approvals = await client.approvals.list({ status: "pending" }); ``` ```bash CLI theme={null} natural approvals list --status pending --limit 50 ``` ```bash cURL theme={null} curl "https://api.natural.com/approvals?status=pending&limit=50" \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` Each record names the payment it holds and every reason it was held: ## Approve or deny [`POST /approvals/{approvalId}/approve`](/api-reference/approvals/approve-payment-or-transfer) releases the original payment. [`POST /approvals/{approvalId}/deny`](/api-reference/approvals/deny-payment-or-transfer) cancels it. ```python Python theme={null} import uuid client.approvals.approve("apr_550e8400e29b41d4a716446655440000", idempotency_key=str(uuid.uuid4())) # or client.approvals.deny("apr_550e8400e29b41d4a716446655440000", idempotency_key=str(uuid.uuid4())) ``` ```typescript TypeScript theme={null} await client.approvals.approve({ approvalId: "apr_550e8400e29b41d4a716446655440000", idempotencyKey: crypto.randomUUID(), }); // or await client.approvals.deny({ approvalId: "apr_550e8400e29b41d4a716446655440000", idempotencyKey: crypto.randomUUID(), }); ``` ```bash CLI theme={null} natural approvals approve --approval-id apr_550e8400e29b41d4a716446655440000 --idempotency-key "$(uuidgen)" # or natural approvals deny --approval-id apr_550e8400e29b41d4a716446655440000 --idempotency-key "$(uuidgen)" ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/approvals/apr_550e8400e29b41d4a716446655440000/approve \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` The record comes back resolved, with `status` now `approved` and `resolvedAt` set: Approving clears only the gate you own. If the payment also breached a gate owned by someone else, it stays held until that owner acts too. When several breached gates share one owner, they merge into one hold that lists every reason, and the highest breached limit is the one you are clearing. # Limits Source: https://docs.natural.com/guides/controls/limits Limit how much each agent can move per transaction, per day, and per month. Limit how much money can move. Account limits gate everything you and your agents do. Agent limits hold one agent to a tighter budget than the rest of your account. Both apply to a single payment (`perTransaction`), a UTC calendar day (`perDay`), or a calendar month (`perMonth`). A limit never automatically fails a payment. A payment that breaches a limit returns a normal `2xx`, no money moves, and it **holds as an approval** until someone approves or denies it. See [Approvals](/guides/controls/approvals). ## Account limits Your account limits are the backstop over everything you and every agent do. ### Read your limits [`GET /parties/me/limits`](/api-reference/parties/get-party-approval-limits) returns the current limits. ```python Python theme={null} limits = client.parties.get_limits() ``` ```typescript TypeScript theme={null} const limits = await client.parties.getLimits(); ``` ```bash CLI theme={null} natural parties getLimits ``` ```text MCP theme={null} What are my account spending limits? ``` ```bash cURL theme={null} curl https://api.natural.com/parties/me/limits \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` ### Set your limits [`PUT /parties/me/limits`](/api-reference/parties/set-party-approval-limits) replaces all three windows at once. ```python Python theme={null} limits = client.parties.set_limits( per_transaction=250_000, # $2,500 per payment per_day=1_000_000, # $10,000 per day per_month=None, # no monthly limit ) ``` ```typescript TypeScript theme={null} const limits = await client.parties.setLimits({ perTransaction: 250_000, // $2,500 per payment perDay: 1_000_000, // $10,000 per day perMonth: null, // no monthly limit }); ``` ```bash CLI theme={null} natural parties setLimits --per-transaction 250000 --per-day 1000000 ``` ```bash cURL theme={null} curl -X PUT https://api.natural.com/parties/me/limits \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "perTransaction": 250000, "perDay": 1000000, "perMonth": null } } }' ``` ### Remove all limits [`DELETE /parties/me/limits`](/api-reference/parties/disable-party-approval-limits) clears every window in one call. To clear just one window, `PUT` that window as `null` and keep the others. Removing your account limits does not touch an agent's own limits or a limit a customer set on your agent; clear those where you set them. ```python Python theme={null} client.parties.disable_limits() ``` ```typescript TypeScript theme={null} await client.parties.disableLimits(); ``` ```bash CLI theme={null} natural parties disableLimits ``` ```bash cURL theme={null} curl -X DELETE https://api.natural.com/parties/me/limits \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` ## Agent limits Give one agent a tighter budget than the rest of your account. Set limits when you [create the agent](/guides/agents/create-agent) with `limits` on [`POST /agents`](/api-reference/agents/create-agent). To change them afterward, [`PATCH /agents/{agentId}`](/api-reference/agents/update-agent). `limits` is a **full replacement**: sending it resets every window to what you pass. ```python Python theme={null} import uuid agent = client.agents.update( "agt_019cd1798d637a4da75dce386343931d", idempotency_key=str(uuid.uuid4()), limits={"per_transaction": 100_000}, # $1,000; clears perDay and perMonth ) ``` ```typescript TypeScript theme={null} const agent = await client.agents.update({ agentId: "agt_019cd1798d637a4da75dce386343931d", idempotencyKey: crypto.randomUUID(), limits: { perTransaction: 100_000 }, // $1,000; clears perDay and perMonth }); ``` ```bash CLI theme={null} natural agents update --agent-id agt_019cd1798d637a4da75dce386343931d \ --idempotency-key "$(uuidgen)" \ --json '{"limits": {"perTransaction": 100000}}' ``` ```bash cURL theme={null} curl -X PATCH https://api.natural.com/agents/agt_019cd1798d637a4da75dce386343931d \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "limits": { "perTransaction": 100000 } } } }' ``` ```json theme={null} { "data": { "type": "agent", "id": "agt_3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f", "attributes": { "name": "Carrier Payment Agent v3.0", "description": "Autonomous agent that pays delivery carriers with enhanced fraud detection", "tags": { "department": "logistics", "release": "v3" }, "handle": "@natural-carrier_payments", "status": "ACTIVE", "limits": { "perTransaction": 100000 }, "createdAt": "2026-01-04T15:30:00Z", "createdBy": "usr_550e8400e29b41d4a716446655440000", "lastActiveAt": "2026-01-05T09:12:00Z" }, "relationships": { "party": { "data": { "type": "party", "id": "pty_7c9e6679e29b41d4a716446655440001" } } } } } ``` See [Invite a customer](/guides/connect/invite-customer) for the full invitation and its request shape. ## How limits compose A payment can pass through several limits: your **account** limit, the **agent** limit, and limits set by your customer. They are independent gates with no precedence: every applicable one must clear, so in effect the tightest limit wins. | Level | Limits | Set by | | -------------------------- | -------------------------------------- | ------------------------------------------ | | **Account** | `perTransaction`, `perDay`, `perMonth` | You, for your whole account | | **Agent** | `perTransaction`, `perDay`, `perMonth` | You, per agent | | **On a customer's behalf** | `perTransaction` only | A customer, when they authorize your agent | A payment that breaches any limit holds for [approval](/guides/controls/approvals). All limits must clear or be approved for a payment to complete. # Wallet access Source: https://docs.natural.com/guides/controls/wallet-access Give agents access to wallets to move money ## Attach an agent Attach the agent with [`POST /wallets/{walletId}/agents`](/api-reference/wallets/grant-agent-access-to-wallet), passing the agent's `id`. ```python Python theme={null} import uuid from naturalpay import Natural client = Natural() grant = client.wallets.attach_agent( "wal_019cd1798d617f65a79cb965dda9eac3", agent_id="agt_019cd1798d637a4da75dce386343931d", idempotency_key=str(uuid.uuid4()), ) print(grant.data.id) ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const grant = await client.wallets.attachAgent({ walletId: "wal_019cd1798d617f65a79cb965dda9eac3", agentId: "agt_019cd1798d637a4da75dce386343931d", idempotencyKey: crypto.randomUUID(), }); console.log(grant.data.id); ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/wallets/wal_019cd1798d617f65a79cb965dda9eac3/agents \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "agentId": "agt_019cd1798d637a4da75dce386343931d" } } }' ``` You cannot attach an agent to the **Vault**. See [Vault](/guides/wallets/vault). ## Set the default wallet Your agents can have default wallets. An agent's default wallet is the one it moves money from when a call names no wallet. Agents getting paid also use the default wallet to collect funds. ```python Python theme={null} client.wallets.set_agent_default_wallet( "wal_019cd1798d617f65a79cb965dda9eac3", "agt_019cd1798d637a4da75dce386343931d", idempotency_key=str(uuid.uuid4()), ) ``` ```typescript TypeScript theme={null} await client.wallets.setAgentDefaultWallet({ walletId: "wal_019cd1798d617f65a79cb965dda9eac3", agentId: "agt_019cd1798d637a4da75dce386343931d", idempotencyKey: crypto.randomUUID(), }); ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/wallets/wal_019cd1798d617f65a79cb965dda9eac3/agents/agt_019cd1798d637a4da75dce386343931d/default \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` ## List the agents a wallet allows Confirm with [`GET /wallets/{walletId}/agents`](/api-reference/wallets/list-wallet-agents) exactly which of your agents can move money from a wallet, and which one holds the default. ```python Python theme={null} agents = client.wallets.list_agents("wal_019cd1798d617f65a79cb965dda9eac3") for agent in agents.data: print(agent.id, agent.attributes.name, agent.meta.wallet_access.is_default) ``` ```typescript TypeScript theme={null} const agents = await client.wallets.listAgents({ walletId: "wal_019cd1798d617f65a79cb965dda9eac3", }); for (const agent of agents.data) { console.log(agent.id, agent.attributes.name, agent.meta.walletAccess.isDefault); } ``` ```bash cURL theme={null} curl https://api.natural.com/wallets/wal_019cd1798d617f65a79cb965dda9eac3/agents \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` ## Detach an agent Revoke access to this one wallet with [`DELETE /wallets/{walletId}/agents/{agentId}`](/api-reference/wallets/detach-agent-from-wallet). The agent stays active on every other wallet it is attached to. You cannot detach an agent from its own default wallet: point its default at another wallet first. ```python Python theme={null} client.wallets.detach_agent( "wal_019cd1798d617f65a79cb965dda9eac3", "agt_019cd1798d637a4da75dce386343931d", idempotency_key=str(uuid.uuid4()), ) ``` ```typescript TypeScript theme={null} await client.wallets.detachAgent({ walletId: "wal_019cd1798d617f65a79cb965dda9eac3", agentId: "agt_019cd1798d637a4da75dce386343931d", idempotencyKey: crypto.randomUUID(), }); ``` ```bash cURL theme={null} curl -X DELETE https://api.natural.com/wallets/wal_019cd1798d617f65a79cb965dda9eac3/agents/agt_019cd1798d637a4da75dce386343931d \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` # Accept payments Source: https://docs.natural.com/guides/flows/accept-payments Turn your agent into a merchant Accepting payments on Natural Your agent can: * Accept debit and credit card payments online * Have funds settle into the same Natural wallets with minimal additional setup * Watch every acceptance settle as a transaction and keep your systems in sync through webhooks Sign up, complete onboarding, and your wallet becomes your settlement account: everything you accept lands there. Read more: [Store funds](/guides/flows/wallets-and-balances). Send a payment request and let the customer pay by link, from their dashboard, or over the phone with voice. Read more: [Request money](/guides/flows/collect-money) and [Payments over Voice](/guides/flows/accept-voice). Every payment becomes a transaction you can list, filter, and reconcile against. Subscribe to webhooks so your systems react the moment money moves. Read more: [Webhooks](/guides/concepts/webhooks). # Voice agents Source: https://docs.natural.com/guides/flows/accept-voice Turn a phone call into a PCI-compliant payment Accepting a payment over Voice on Natural Card payments have historically been difficult over voice due to PCI exposure. Let Natural's agent handle it for you and notify your agent when the payment is processed. Your agent can: * Call transfer into Natural's agent, and have a transfer back when finished * Collect spoken card details spoken by the caller and process the payment on the spot * Confirm the total out loud, then settle the payment into your wallet with the same transactions and webhooks as everything else Voice acceptance rides on the same merchant setup as every other rail: sign up, complete onboarding, and settle into your wallet. In addition, you'll need to add SIP transfer routing into your voice agents. Read more: [Accept payments](/guides/flows/accept-payments). When it comes time for payment collection, transfer the call to Natural's agent and rely on Natural to take in card details and process the payment in a PCI-compliant manner. The agent reads back the amount, takes the caller's confirmation, and the payment settles into your wallet. Track it in your transactions and react through webhooks. Read more: [Webhooks](/guides/concepts/webhooks). # Overview Source: https://docs.natural.com/guides/flows/build-platform Build a payments platform for your customers Building a platform on Natural Connect Your agent can use all of Natural's products on behalf of your customers with Connect. * Request and accept payments over ACH, card, wire, RTP, FedNow, and more. * Hold funds in FDIC-insured1 wallets * Make payments to businesses, individuals, and merchants The most effective platform agents on Natural combine many of these flows. **[Embedded finance](/guides/flows/platform-embedded-finance)** — Request, collect, and pay funds inside each customer's own wallet, so money moves through your product with balances held in the customer's name. **[AP/AR](/guides/flows/platform-ap-ar)** — Run a customer's payables and receivables end to end: collect what's owed into their wallet, pay their bills from it, and reconcile both sides. **[Agentic commerce](/guides/flows/platform-agentic-commerce)** — Transact online on your customer's behalf, accepting and paying counterparties at machine speed. **[Treasury management](/guides/flows/platform-treasury)** — Put a customer's idle cash to work and move it between their accounts under the controls they delegated. Your agent can run these flows for your customers, and more, whether they are businesses or individuals. First, [sign up](https://natural.com/signup) for a Natural account. If you're incorporated, sign up as a Business so you can invite team members. Then, [add your agent on Natural](/guides/agents/create-agent). This is the identity your customers delegate permissions to. It builds a transaction history, so your agent's payment activity stays observable to you through Natural. Invite your customers so your agent can move money on their behalf. Your agent can invite customers through any of the Natural tools it has access to (MCP, CLI, API, SDK). You can also do this manually, through the Natural dashboard. The customer receives an invitation email showing the permissions your agent is asking for. Your customer will sign on up Natural, with Natural handling the compliance. Read more: [Invite a customer](/guides/connect/invite-customer) and [Set agent limits](/guides/controls/limits). With your agent registered and your customers connected, it can accept and make payments for them, manage their balances, and read their activity, all within what each customer granted. Read more: [Move money for a customer](/guides/connect/move-money-for-customer).

1 Natural is a financial technology company, not an FDIC-insured depository institution. FDIC deposit insurance covers the failure of an insured depository institution. Certain conditions must be satisfied for pass-through FDIC insurance to apply. Deposits in Wallet accounts are FDIC-insured through Column N.A., Member FDIC, and Column's Sweep Program Network Banks.

# Request money Source: https://docs.natural.com/guides/flows/collect-money Your agent requests from anyone by email, phone, or handle Requesting money on Natural Your agent can: * Request money from anyone by email, phone, `@handle`, or party ID * Payers can fulfill on any rail: ACH, wire, card, and more * Tell you the moment a request is paid For business customers, Natural auto-reconciles customer payments against the right request so your receivables match themselves. Address the payer by email, phone, `@handle`, or party ID, set the amount, and send the request over Natural. Read more: [Request a payment](/guides/payments/request-payment). Natural routes the request the right way: payers on Natural see it in their dashboard, and payers who aren't get a pay link. They pay it or decline it and you can cancel an open request any time. Either you or the recipient can choose between available payment methods for the request. Track each request from open to fulfilled. The fulfilling payment arrives in your wallet like any other. Read more: [Track a payment](/guides/payments/track-payment). # Issue cards Source: https://docs.natural.com/guides/flows/issue-cards Give your agents cards to pay merchants online Issuing cards on Natural Issue cards on Natural when your agent needs to pay a merchant that only takes card. Debit cards draw from your wallet balances; charge cards extend a credit limit you pay in full each statement period. Your agent can: * Issue debit or charge cards tied to a wallet you choose * Receive tokenized card credentials scoped to a single purchase, without handling raw card data * Spend only within the per-transaction, daily, and monthly limits you set, with anything over surfaced for your approval Set limits on what your agent can move per transaction, per day, and per month. Anything over a limit stops and waits for a human approval. Read more: [Set agent limits](/guides/controls/limits) and [Approvals](/guides/controls/approvals). Debit cards spend from wallet balances. Link a bank account, deposit over ACH, or move money between wallets so the card has funds to draw on. Read more: [Store funds](/guides/flows/wallets-and-balances) and [Deposit and withdraw](/guides/transfers/deposit-and-withdraw). Attach the card to an agent and the wallet it may spend from. Your agent gets credentials it can use at checkout without ever seeing a full card number. Read more: [Manage agent wallet access](/guides/controls/wallet-access) and [Create an agent](/guides/agents/create-agent). Every card purchase becomes a transaction you can list, filter, and reconcile against. Subscribe to webhooks so your systems react the moment money moves. Read more: [Webhooks](/guides/concepts/webhooks). # Agentic commerce Source: https://docs.natural.com/guides/flows/platform-agentic-commerce An agent that transacts in the open market on your customer's behalf, accepting payments and paying counterparties at machine speed Agentic commerce is when your agent transacts in the open market on a customer's behalf, accepting payments and paying out to counterparties at machine speed, across more of them than a human desk can manage. Your agent can: * Accept payments from a customer's buyers by payment request or over Voice * Pay publishers, suppliers, creators, and merchants from the customer's wallet * Reach counterparties who aren't on Natural yet; Natural sends them a claim link * Issue cards to buy directly where a card is required * Keep spend inside the limits each customer delegated, holding large payments for approval Where this shows up: * **Internet media buying** — Brokers ad placements for advertisers, paying publishers, networks, and creators from each advertiser's wallet as campaigns perform. * **Catering and events** — Takes a booking deposit over Voice, then pays venues, rentals, and staffing from the caterer's wallet. * **Procurement and purchasing** — Buys inventory or supplies on a merchant's behalf, issuing cards or paying suppliers directly. You own the market logic (targeting, sourcing, pricing, and fulfillment), and Natural moves each customer's money under the controls they set. # AP/AR Source: https://docs.natural.com/guides/flows/platform-ap-ar Run a customer's payables and receivables end to end: collect what's owed, pay the bills, and reconcile both sides AP/AR is running a customer's payables and receivables end to end: your agent collects what's owed into the customer's wallet, pays the bills they owe from it, and reconciles both sides against your system of record. Your agent can: * Request and collect receivables into the customer's wallet by email, phone, @handle, or party ID * Collect on any rail the payer chooses; Natural auto-reconciles wires and other payments against the right request using the one-time instructions it generates for each one * Pay the customer's vendors and bills from the same wallet, holding large payments for approval * Match every settled payment to an open invoice against a single transaction feed, and surface exceptions Where this shows up: * **Cash application** — Collect inbound payments that arrive already matched to each open receivable, so reconciliation happens automatically. * **Bill pay** — Schedule and pay vendor bills from the customer's wallet, with approvals on anything large. * **B2B invoicing** — Issue invoices, collect on any rail, and track who has paid and who is still open. You own the ledger (invoices, terms, matching rules, and approvals), and Natural moves the money and hands back a reconcilable record of every payment in and out. # Embedded finance Source: https://docs.natural.com/guides/flows/platform-embedded-finance Request, collect, and pay funds inside each customer's own wallet, with balances held in the customer's name Natural is built for every kind of embedded finance flow. Your agent can: * Request payments into a customer's wallet by email, phone, @handle, or party ID * Pay the customer's vendors, contractors, and counterparties from that same wallet * Stage funds in the customer's Natural wallet between collection and payout * Surface unusually large payments for approval before money moves Where this shows up: * **Property management** — Collect rent into an owner's wallet, then pay their contractors and owner distributions from it. * **Field and home services** — Collect job payments from homeowners, then pay the pros who did the work. * **Marketplaces** — Settle buyer payments into each seller's wallet and disburse payouts on your schedule. The pattern is the same across all of them: you own the product logic (invoices, schedules, approvals), and Natural moves each customer's money under the permissions they delegated. # Treasury management Source: https://docs.natural.com/guides/flows/platform-treasury Put a customer's idle cash to work: link their accounts, stage funds in a Natural wallet, and move cash under the controls they delegated Treasury management is about keeping a customer's cash working. Your agent watches balances across the customer's accounts and moves idle cash between them: into a Natural wallet to stage it, out to the destination your policy picks, and back when the customer needs liquidity. Your agent can: * Use your Plaid connections to the customer's operating and yield-bearing accounts * Sweep idle cash from the operating account into the customer's Natural wallet to stage it * Move staged cash to the destination your policy selects, or back on demand * Operate entirely within the per-transaction and periodic limits the customer delegated Where this shows up: * **Wealth and cash management** — Sweeps a client's idle balances into higher-yield accounts and back as they spend. * **Corporate treasury** — Concentrates cash from operating accounts and positions it against upcoming obligations. * **Marketplace and platform float** — Stages collected balances and moves them on a schedule you control. You own the strategy (which yields to chase, target balances, sweep timing, and suitability), and Natural carries out the movement so cash keeps working without ever leaving the customer's ownership. # Send payments Source: https://docs.natural.com/guides/flows/send-money Your agent pays anyone by email, phone, or handle Sending a payment on Natural Your agent can: * Pay anyone by email, phone, `@handle`, or party ID * Settle directly to recipients on Natural, or send a payment claim to recipients new to Natural * Spend only within the per-transaction, daily, and monthly limits you set, with anything over surfaced for your approval Set limits on what your agent can move per transaction, per day, and per month. Anything over a limit stops and waits for a human approval. Read more: [Set agent limits](/guides/controls/limits) and [Approvals](/guides/controls/approvals). Address the recipient by email, phone, `@handle`, or party ID. If they're on Natural, the payment settles directly. If they're not, it reaches them as a claim and onboards them when they accept. Read more: [Send a payment](/guides/payments/send-payment). Every payment has a status you can follow from sent to settled via dashboard or webhook. Read more: [Track a payment](/guides/payments/track-payment). # Store funds Source: https://docs.natural.com/guides/flows/wallets-and-balances Hold money on Natural and let your agent manage it Wallet balances in the Natural dashboard Natural wallets hold your money in FDIC-insured1 accounts. You have access to Standard wallets as well as a Vault. Money held in a Vault can't be moved out by an agent. Your agent can: * Check balances and watch activity across each wallet you give it access to * Transfer between your wallets, like topping up a spending wallet from Vault * Fund payments from the wallet you choose, within transaction or velocity limits you set Your party gets one automatically. It receives payments and funds outgoing ones unless you say otherwise. Connect an external account and fund your wallet. Read more: [Deposit and withdraw](/guides/transfers/deposit-and-withdraw). Create standard wallets to separate spend and keep reserves in the Vault. Make any wallet the default. Read more: [Wallets](/guides/wallets/wallets) and [Vault](/guides/wallets/vault). Attach an agent to a wallet and it can check balances, move money between your wallets, and fund payments. Agents always respect limits you set. Read more: [Manage agent wallet access](/guides/controls/wallet-access) and [Set agent limits](/guides/controls/limits).

1 Natural is a financial technology company, not an FDIC-insured depository institution. FDIC deposit insurance covers the failure of an insured depository institution. Certain conditions must be satisfied for pass-through FDIC insurance to apply. Deposits in Wallet accounts are FDIC-insured through Column N.A., Member FDIC, and Column's Sweep Program Network Banks.

# Compliance Source: https://docs.natural.com/guides/overview/compliance Regulatory compliance and identity verification Federal and state regulations require Natural to verify identity, so every [Party](/guides/concepts/parties) must be verified before it can send payments. Verification runs automatically once you submit your details during onboarding, usually in seconds, sometimes up to 48 hours. After that, Natural meets BSA/AML rules (Bank Secrecy Act / Anti-Money Laundering) through automated transaction monitoring. ## Verification types For individuals. Individual parties provide: * Full legal name * Date of birth * Social Security Number or ITIN * Residential address * Country of citizenship * Employment status and job title * Annual income * Email and phone (both verified during onboarding) For businesses. Business parties provide: * Legal business name * Trade name (DBA), if the business uses one * EIN (Employer Identification Number) * Business type (LLC, Corporation, etc.) * Industry (NAICS code) * State and date of incorporation * Registered address and physical address * Countries of operations * Source of funds * Website Businesses must disclose every individual who owns 25% or more or holds significant control (their ultimate beneficial owners, or UBOs). For each UBO, Natural collects the same KYC details: * Full legal name * Date of birth * Social Security Number or ITIN * Residential address * Email and phone * Ownership percentage * Title (CEO, Owner, etc.) * Country of citizenship * Employment status and annual income ## Identity documents Most verifications complete from the information above alone. When automated verification needs more, Natural requests supporting documents, typically a government-issued ID (driver's license or passport) for an individual or UBO, or formation documents such as articles of incorporation for a business. These are uploaded securely through onboarding. All personal and business information is encrypted in transit and at rest, and sensitive data like SSNs is tokenized, never stored in plain text. ## Related concepts * [Parties](/guides/concepts/parties) - Identity types that require verification * [Wallets](/guides/concepts/wallets) - How funds are held and escrowed # Security Source: https://docs.natural.com/guides/overview/security How Natural protects your identity and banking details, and controls who can move money Natural holds sensitive data and moves money, so security is built into how that data is protected and how every action is authorized. ## Data protection * All API traffic is encrypted in transit with TLS 1.3. * Data at rest is encrypted with AES-256 using dedicated AWS KMS keys that rotate automatically. * The most sensitive identifiers, such as Social Security and tax ID numbers and bank account numbers, are additionally encrypted at the field level with keys held outside Natural's systems, so their plaintext is never readable from Natural's database. ## Authentication and access * Access uses API keys, agent keys, or OAuth for MCP clients, and every credential is limited to scopes that can never exceed the permissions of the party that issued it. * Keys are high-entropy random values, shown once when they are created and stored only as one-way hashes. * OAuth uses the 2.1 flow with PKCE and short-lived access tokens, and reusing a rotated refresh token revokes the session. * Access tokens are signed by AWS KMS, and the signing key never leaves KMS. See [Authentication](/api-reference/authentication) for how credentials and scopes work. ## Agent authority An agent can act only with the permissions and limits a party grants it, and Natural enforces that grant on every request. * A party grants an agent specific permissions and [spending limits](/guides/controls/limits), and lowering a limit or removing a permission takes effect immediately. * A payment that would exceed a limit is held for [approval](/guides/controls/approvals) rather than dropped. * Every agent payment records the agent's verified identity and a run identifier, so it traces back to the exact agent and run that made it. * Permissions and limits are enforced on Natural's servers rather than in your code, so a compromised or misbehaving agent cannot exceed what it was granted. ## Related * [Compliance](/guides/overview/compliance) - Identity verification and the data we collect * [Reliability](/guides/platform/reliability) - Availability and behavior under failure * [Authentication](/api-reference/authentication) - Credentials, scopes, and attribution # Getting started Source: https://docs.natural.com/guides/overview/start-here Explore common use cases and integrate Natural immediately ## Common use cases Hold money on Natural and let your agent manage it Your agent pays anyone by email, phone, or handle Send payment requests and watch the money land Turn your agent into a merchant that can accept payments Give your agents cards online Move money on behalf of your customers ## Using Natural Sign up at [natural.com/signup](https://natural.com/signup) and complete onboarding in the [dashboard](/guides/platform/dashboard). If an AI assistant operates Natural for you, connect it to the hosted MCP server at `mcp.natural.com`. Pick your tool: Custom connectors require a paid Claude plan (Pro, Max, Team, or Enterprise). The setup is the same for [claude.ai](https://claude.ai) and the Claude Desktop app. 1. In [claude.ai](https://claude.ai) or the Claude Desktop app, open the sidebar, select **Customize**, and go to the **Connectors** tab. 2. Select **Add custom connector** and fill in the fields: name `Natural`, remote MCP server URL `https://mcp.natural.com`. 3. Select **Add**, then **Connect**. Your browser opens Natural's authorization page; approve it, and you are redirected back to Claude. 4. Try it in chat: ```text theme={null} Use Natural to check my wallet balance. ``` Custom plugins require a paid ChatGPT plan (Plus, Pro, Business, or Enterprise). 1. Open **Settings → Security** and turn on **Developer mode**. 2. Select **Plugins** in the sidebar, then the **+** button, and fill in the fields: name `Natural`, MCP server URL `https://mcp.natural.com`, authentication **OAuth**. 3. Accept ChatGPT's unverified-server disclaimer and select **Create**, then **Sign in with Natural**. Approve Natural's authorization page when it opens. 4. Try it in chat: ```text theme={null} Use Natural to check my wallet balance. ``` 1. Add Natural's hosted MCP server and sign in with OAuth. Your browser opens Natural's authorization page; approve it: ```bash theme={null} claude mcp add --transport http natural https://mcp.natural.com --scope user && claude mcp login natural ``` 2. Start a fresh Claude Code session and run `/mcp`. Confirm `natural` shows as connected and authenticated. If it's not, run `claude mcp login natural` again to re-authorize. 3. Try it: ```text theme={null} Use Natural to check my wallet balance. ``` 1. Add Natural as a remote HTTP server, then approve Natural's authorization page in your browser. If the browser does not open on its own, run `codex mcp login natural`: ```bash theme={null} codex mcp add natural --url https://mcp.natural.com ``` 2. Start a fresh Codex session and run `/mcp`. Confirm `natural` shows as connected and authenticated. If it's not, run `codex mcp login natural` again to re-authorize. 3. Try it: ```text theme={null} Use Natural to check my wallet balance. ``` 1. Open the Codex app and go to **Plugins** in the sidebar. 2. Open the dropdown (⌄) in the top right, select **Add marketplace**, and paste this into the **Source** field: ```text theme={null} naturalpay/agent-plugins ``` 3. Switch to **Personal** and select **Install**. Your browser opens Natural's authorization page; approve it. 4. Try it in chat: ```text theme={null} Use Natural to check my wallet balance. ``` 1. In Cursor, open **Settings → Tools & MCPs**, select **New MCP Server** (this opens `mcp.json`), merge Natural into the config, and save: ```json theme={null} { "mcpServers": { "natural": { "url": "https://mcp.natural.com" } } } ``` 2. Find **Natural** under **Settings → Tools & MCPs** and connect it. Your browser opens Natural's authorization page; approve it, and you are redirected back to Cursor. If Natural doesn't appear, restart Cursor. 3. Try it: ```text theme={null} Use Natural to check my wallet balance. ``` For headless or custom agents that can't sign in with browser OAuth: [create an agent](/guides/agents/create-agent) and issue a key, then use it as the bearer token against `mcp.natural.com` or the [REST API](/api-reference/about). See the [API-key fallback](/guides/platform/mcp#api-key-fallback) for details. Using a host that is not listed? Any MCP-aware host with remote-server OAuth works; see [Any other MCP-aware host](/guides/platform/mcp#any-other-mcp-aware-host) in the MCP guide, which also covers the CLI OAuth and API-key fallbacks. You're writing software that uses Natural: a backend, a SaaS, an agent runtime, a CI script. **1.** Generate an API key from the **Developers** tab of the [dashboard](/guides/platform/dashboard). The key is shown once; store it in a secret manager and never commit it. **2.** Install for your runtime: ```bash Python theme={null} pip install naturalpay ``` ```bash TypeScript theme={null} npm install @naturalpay/sdk ``` ```bash CLI theme={null} curl -fsSL https://natural.com/install.sh | bash ``` **3.** No SDK for your language? Use the [REST API](/api-reference/about) at `api.natural.com` with the same key. # Fulfill a payment request Source: https://docs.natural.com/guides/payments/fulfill-or-decline-request Pay a request addressed to you from a wallet or bank account ## See what's waiting on you List the requests addressed to you with [`GET /payment-requests/incoming`](/api-reference/paymentrequests/list-incoming-payment-requests). ```python Python theme={null} from naturalpay import Natural client = Natural() requests = client.payment_requests.list_incoming() ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const requests = await client.paymentRequests.listIncoming(); ``` ```bash CLI theme={null} natural payment-requests listIncoming ``` ```bash cURL theme={null} curl https://api.natural.com/payment-requests/incoming \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` Each item's `id` (`prq_*`) is what you fulfill or decline below. A `status` of `OPEN` means it's still waiting on you. ## Fulfill it [`POST /payment-requests/{paymentRequestId}/fulfill`](/api-reference/paymentrequests/fulfill-payment-request) pays the request and moves the money. The amount comes from the request itself. You only choose where it is paid from, so set `paymentSource` to a wallet you own. ```python Python theme={null} import uuid from naturalpay import Natural agent_client = Natural( x_agent_id="agt_019cd1798d637a4da75dce386343931d", x_instance_id=str(uuid.uuid4()), ) payment = agent_client.payment_requests.fulfill( "prq_550e8400e29b41d4a716446655440000", payment_source={"type": "wallet", "wallet_id": "wal_550e8400e29b41d4a716446655440000"}, idempotency_key=str(uuid.uuid4()), ) ``` ```typescript TypeScript theme={null} const payment = await client.paymentRequests.fulfill( { paymentRequestId: "prq_550e8400e29b41d4a716446655440000", paymentSource: { type: "wallet", walletId: "wal_550e8400e29b41d4a716446655440000" }, idempotencyKey: crypto.randomUUID(), }, { xAgentId: "agt_019cd1798d637a4da75dce386343931d", xInstanceId: crypto.randomUUID(), }, ); ``` ```bash CLI theme={null} natural payment-requests fulfill \ --payment-request-id prq_550e8400e29b41d4a716446655440000 \ --json '{"paymentSource": {"type": "wallet", "walletId": "wal_550e8400e29b41d4a716446655440000"}}' \ --x-agent-id agt_019cd1798d637a4da75dce386343931d \ --x-instance-id "$(uuidgen)" \ --idempotency-key "$(uuidgen)" ``` ```text MCP theme={null} Fulfill payment request prq_550e8400e29b41d4a716446655440000 from my main wallet. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/payment-requests/prq_550e8400e29b41d4a716446655440000/fulfill \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "X-Agent-ID: agt_019cd1798d637a4da75dce386343931d" \ -H "X-Instance-ID: $(uuidgen)" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "paymentSource": { "type": "wallet", "walletId": "wal_550e8400e29b41d4a716446655440000" } } } }' ``` Fulfilling returns the resulting **payment** (`pay_*`), which starts in `PROCESSING`. [Track it](/guides/payments/track-payment) to confirm it settles. You can also fulfill from a linked bank account instead of a wallet. ## Decline it [`POST /payment-requests/{paymentRequestId}/decline`](/api-reference/paymentrequests/decline-payment-request) turns the request down. No money moves, the requester is notified, and the request's `status` becomes `DECLINED`. Declining is final and cannot be undone. ```python Python theme={null} import uuid declined = client.payment_requests.decline( "prq_550e8400e29b41d4a716446655440000", idempotency_key=str(uuid.uuid4()), ) ``` ```typescript TypeScript theme={null} const declined = await client.paymentRequests.decline({ paymentRequestId: "prq_550e8400e29b41d4a716446655440000", idempotencyKey: crypto.randomUUID(), }); ``` ```bash CLI theme={null} natural payment-requests decline \ --payment-request-id prq_550e8400e29b41d4a716446655440000 \ --idempotency-key "$(uuidgen)" ``` ```text MCP theme={null} Decline payment request prq_550e8400e29b41d4a716446655440000. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/payment-requests/prq_550e8400e29b41d4a716446655440000/decline \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` # Request a payment Source: https://docs.natural.com/guides/payments/request-payment Collect money from anyone with a hosted payment link that Natural delivers for you. ## Request the payment Create the request with [`POST /payment-requests`](/api-reference/paymentrequests/create-payment-request). Address the `payer` by email, phone, party id, agent id, or [handle](/guides/agents/handles), the same set you use for a payment counterparty. ```python Python theme={null} import uuid from naturalpay import Natural agent_client = Natural( x_agent_id="agt_019cd17a4f2e7b9c8d6e5f4a3b2c1d0e", x_instance_id=str(uuid.uuid4()), ) request = agent_client.payment_requests.create( amount=2500, currency="USD", description="Invoice 7", payer_name="Natural Client", payer={"type": "email", "value": "client@example.com"}, idempotency_key=str(uuid.uuid4()), ) ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const request = await client.paymentRequests.create( { amount: 2500, currency: "USD", description: "Invoice 7", payerName: "Natural Client", payer: { type: "email", value: "client@example.com" }, idempotencyKey: crypto.randomUUID(), }, { xAgentId: "agt_019cd17a4f2e7b9c8d6e5f4a3b2c1d0e", xInstanceId: crypto.randomUUID(), }, ); ``` ```bash CLI theme={null} natural payment-requests create \ --amount 2500 \ --currency USD \ --description "Invoice 7" \ --payer-name "Natural Client" \ --params '{"payer": {"type": "email", "value": "client@example.com"}}' \ --x-agent-id agt_019cd17a4f2e7b9c8d6e5f4a3b2c1d0e \ --x-instance-id "$(uuidgen)" \ --idempotency-key "$(uuidgen)" ``` ```text MCP theme={null} Using my Procurement Agent, request $25 from client@example.com for Invoice 7. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/payment-requests \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "X-Agent-ID: agt_019cd17a4f2e7b9c8d6e5f4a3b2c1d0e" \ -H "X-Instance-ID: $(uuidgen)" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "amount": 2500, "currency": "USD", "description": "Invoice 7", "payerName": "Natural Client", "payer": { "type": "email", "value": "client@example.com" } } } }' ``` The response carries the request `id` (`prq_*`) and the `paymentLinkUrl`. Natural delivers the payment request over email or phone number. ## Track it Check status any time with [`GET /payment-requests/{paymentRequestId}`](/api-reference/paymentrequests/get-payment-request). A request stays `OPEN` until the payer settles it. ```python Python theme={null} request = client.payment_requests.get(request.data.id) ``` ```typescript TypeScript theme={null} const status = await client.paymentRequests.get({ paymentRequestId: request.data.id, }); ``` ```bash CLI theme={null} natural payment-requests get --payment-request-id prq_550e8400e29b41d4a716446655440000 ``` ```text MCP theme={null} What's the status of that payment request? ``` ```bash cURL theme={null} curl https://api.natural.com/payment-requests/prq_550e8400e29b41d4a716446655440000 \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` ## Cancel it Cancel an open request with [`POST /payment-requests/{paymentRequestId}/cancel`](/api-reference/paymentrequests/cancel-payment-request) to void its payment link and move it to `CANCELED`. Once a payer fulfills one, it's money movement and settles normally. ```python Python theme={null} import uuid canceled = client.payment_requests.cancel( request.data.id, idempotency_key=str(uuid.uuid4()), ) ``` ```typescript TypeScript theme={null} const canceled = await client.paymentRequests.cancel({ paymentRequestId: request.data.id, idempotencyKey: crypto.randomUUID(), }); ``` ```bash CLI theme={null} natural payment-requests cancel \ --payment-request-id prq_550e8400e29b41d4a716446655440000 \ --idempotency-key "$(uuidgen)" ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/payment-requests/prq_550e8400e29b41d4a716446655440000/cancel \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` # Send a payment Source: https://docs.natural.com/guides/payments/send-payment Pay anyone by email, phone, party ID, agent ID, or @handle. Address the recipient by email, phone, party id, agent id, or [handle](/guides/agents/handles). If they are new to Natural, Natural sends a claim link and onboards them when they claim the funds. ```python Python theme={null} import uuid from naturalpay import Natural agent_client = Natural( x_agent_id="agt_019cd1798d637a4da75dce386343931d", x_instance_id=str(uuid.uuid4()), ) payment = agent_client.payments.create( amount=500_000, currency="USD", counterparty={"type": "email", "value": "contractor@example.com"}, description="Q4 development work", idempotency_key=str(uuid.uuid4()), ) print(payment.data.id) ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const payment = await client.payments.create( { amount: 500_000, currency: "USD", counterparty: { type: "email", value: "contractor@example.com" }, description: "Q4 development work", idempotencyKey: crypto.randomUUID(), }, { xAgentId: "agt_019cd1798d637a4da75dce386343931d", xInstanceId: crypto.randomUUID(), }, ); console.log(payment.data.id); ``` ```bash CLI theme={null} natural payments create \ --amount 500000 \ --currency USD \ --params '{"counterparty": {"type": "email", "value": "contractor@example.com"}}' \ --description "Q4 development work" \ --x-agent-id agt_019cd1798d637a4da75dce386343931d \ --x-instance-id "$(uuidgen)" \ --idempotency-key "$(uuidgen)" ``` ```text MCP theme={null} Using my Procurement Agent, pay contractor@example.com $5,000 for Q4 development work. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/payments \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "X-Agent-ID: agt_019cd1798d637a4da75dce386343931d" \ -H "X-Instance-ID: $(uuidgen)" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "amount": 500000, "currency": "USD", "counterparty": { "type": "email", "value": "contractor@example.com" }, "description": "Q4 development work" } } }' ``` The response returns the payment `id` (`pay_*`) and its initial `status`: A recipient who is not on Natural yet comes back as `PENDING_CLAIM`. Natural emails or texts them a claim link and moves the money once they onboard and claim it. The payment draws from the sending party's default wallet. Pass `walletId` (`wal_*`) to spend from a specific wallet instead. Once sent, [track the payment](/guides/payments/track-payment) to follow it from `PROCESSING` to `COMPLETED`, or cancel it before it settles. # Track a payment Source: https://docs.natural.com/guides/payments/track-payment Follow a payment through its lifecycle. Follow a payment you [sent](/guides/payments/send-payment) or [requested](/guides/payments/request-payment) through its lifecycle, list every money movement on your account, and cancel a payment before it settles. ## Get one payment Look up a payment with [`GET /payments/{paymentId}`](/api-reference/payments/get-payment) to read its current status. ```python Python theme={null} payment = client.payments.get("pay_550e8400e29b41d4a716446655440000") status = payment.data.attributes.status ``` ```typescript TypeScript theme={null} const payment = await client.payments.get({ paymentId: "pay_550e8400e29b41d4a716446655440000", }); const status = payment.data.attributes.status; ``` ```bash CLI theme={null} natural payments get --payment-id pay_550e8400e29b41d4a716446655440000 ``` ```text MCP theme={null} What's the status of payment pay_550e8400e29b41d4a716446655440000? ``` ```bash cURL theme={null} curl https://api.natural.com/payments/pay_550e8400e29b41d4a716446655440000 \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` The response carries the payment's `status`, amount, and the parties on each side: A healthy payment moves `CREATED` to `PROCESSING` to `COMPLETED`. It can pause at `PENDING_CLAIM` while it waits for a new recipient to claim the funds, or at `IN_REVIEW` during a compliance hold. It ends at `FAILED`, `RETURNED`, `CANCELED`, or `APPROVAL_DENIED`. ## List all money movement [`GET /transactions`](/api-reference/transactions/list-transactions) returns all your transactions: every payment, transfer, deposit, and withdrawal, in reverse-chronological order. ```python Python theme={null} transactions = client.transactions.list(limit=20) ``` ```typescript TypeScript theme={null} const transactions = await client.transactions.list({ limit: 20 }); ``` ```bash CLI theme={null} natural transactions list --limit 20 ``` ```text MCP theme={null} List my recent transactions. ``` ```bash cURL theme={null} curl "https://api.natural.com/transactions?limit=20" \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` To read a customer's transactions instead of your own, add the `customerPartyId` query param. ## Cancel a payment Cancel a payment with [`POST /payments/{paymentId}/cancel`](/api-reference/payments/cancel-payment) while it is still `PENDING_CLAIM`. ```python Python theme={null} import uuid agent_client = Natural( x_agent_id="agt_019cd1798d637a4da75dce386343931d", x_instance_id=str(uuid.uuid4()), ) canceled = agent_client.payments.cancel( "pay_550e8400e29b41d4a716446655440000", idempotency_key=str(uuid.uuid4()), ) ``` ```typescript TypeScript theme={null} const canceled = await client.payments.cancel( { paymentId: "pay_550e8400e29b41d4a716446655440000", idempotencyKey: crypto.randomUUID(), }, { xAgentId: "agt_019cd1798d637a4da75dce386343931d", xInstanceId: crypto.randomUUID(), }, ); ``` ```bash CLI theme={null} natural payments cancel \ --payment-id pay_550e8400e29b41d4a716446655440000 \ --x-agent-id agt_019cd1798d637a4da75dce386343931d \ --x-instance-id "$(uuidgen)" \ --idempotency-key "$(uuidgen)" ``` ```text MCP theme={null} Cancel payment pay_550e8400e29b41d4a716446655440000. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/payments/pay_550e8400e29b41d4a716446655440000/cancel \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "X-Agent-ID: agt_019cd1798d637a4da75dce386343931d" \ -H "X-Instance-ID: $(uuidgen)" \ -H "Idempotency-Key: $(uuidgen)" ``` The payment moves to `CANCELED` and no funds leave your wallet: Cancellation only works before settlement. Once a payment reaches `PROCESSING`, the funds are already moving and it can no longer be canceled. # CLI Source: https://docs.natural.com/guides/platform/cli Test, provision, and debug the Natural API from your terminal while you build an agent The `natural` CLI is the fastest way to exercise the Natural API while you build an agent: run a call before you wire it into code, and set up the agent and wallets your code will run against. Every resource command maps one-to-one to an API endpoint. ## Install The install script auto-detects your OS and architecture and needs no toolchain. macOS and Linux: ```bash theme={null} curl -fsSL https://natural.com/install.sh | bash ``` The installer downloads the release artifact for your platform, verifies its checksum, and installs `natural` to `~/.natural/bin`, adding that directory to your shell profile when possible. Confirm it: ```bash theme={null} natural --version ``` Restart your shell after installation if `natural` is not immediately found on your `PATH`. To pin a version, run `curl -fsSL https://natural.com/install.sh | NATURAL_VERSION=x.y.z bash`. Natural publishes `.zip` archives for macOS and `.tar.gz` archives for Linux, with Intel and ARM builds, under `https://natural.com/install/v/`. Check the [current version](https://natural.com/install/VERSION). ## Update Once installed, `natural update` upgrades the CLI in place: ```bash theme={null} natural update ``` ## Authenticate For local, interactive use, sign in with browser OAuth: ```bash theme={null} natural login natural status ``` `natural login` opens a Natural authorization page, returns through a local redirect, and stores OAuth credentials on your machine. Access tokens are short-lived and refreshed automatically, so you can use the full CLI without creating or pasting an API key. Use an API key for CI, non-interactive scripts, SDK/REST integrations, or as an explicit override: ```bash theme={null} export NATURAL_API_KEY=sk_ntl_prod_abc123... ``` Get a key from the **Developers** tab of the [dashboard](/guides/platform/dashboard). Production keys are prefixed `sk_ntl_prod_`. Confirm it works: ```bash theme={null} natural wallets list ``` Every command is `natural [flags]`. Add `--help` to any command for its flags. ## Test in the sandbox Point the CLI at the [sandbox](/api-reference/sandbox/overview) with a sandbox key, and use the `natural simulations` commands to act as the counterparty: ```bash theme={null} export NATURAL_API_KEY=sk_ntl_sandbox_abc123... export NATURAL_BASE_URL=https://api.sandbox.natural.com natural simulations inviteCustomer \ --json '{"agentIds": ["agt_019cd1798d637a4da75dce386343931d"]}' \ --idempotency-key "$(uuidgen)" ``` See [Sandbox from MCP, CLI, and SDKs](/api-reference/sandbox/surfaces) for the full simulation surface. ## Test a call before you code it Run an endpoint from the terminal and see the real response shape before you wire it into your agent. `--debug` prints the full HTTP request and response, the same call your SDK will make: ```bash theme={null} natural payments create \ --amount 500000 \ --currency USD \ --params '{"counterparty": {"type": "email", "value": "contractor@example.com"}}' \ --description "Q4 development work" \ --idempotency-key "$(uuidgen)" \ --debug ``` `--amount` is in cents. `counterparty` is a typed object (`email`, `phone`, `party_id`, `agent_id`, or `handle`) passed through `--params`, or send the whole body with `--json`. Reusing an `--idempotency-key` safely returns the original result instead of charging twice. ## Provision what your agent runs against Your agent needs an agent identity, a funded wallet, and (when it acts for customers) customer connections. Set them up once: ```bash theme={null} # Create the agent identity your code will run as natural agents create \ --name "Natural Bot" \ --description "Pays delivery carriers" \ --idempotency-key "$(uuidgen)" # Pull funds into your wallet from a linked bank account natural transfers initiateDeposit \ --amount 5000000 \ --external-account-id eac_550e8400e29b41d4a716446655440000 \ --idempotency-key "$(uuidgen)" # Invite a customer to authorize your agent natural customers createInvitations --json '{ "recipients": [{ "type": "email", "value": "customer@example.com" }], "agents": [{ "agentId": "agt_019cd1798d637a4da75dce386343931d", "permissions": ["payments.create"] }] }' ``` Link a bank account from the **Wallet** tab of the dashboard first; `natural external-accounts list` then gives you the `eac_*` id to deposit from. ## Check what your agent sees When you're debugging agent behavior, inspect the same state your agent reads: ```bash theme={null} # Wallet balances + ACH deposit instructions natural wallets list # Transaction history: --type is payment, transfer, or all natural transactions list --limit 20 --type payment # A single transaction's status natural transactions get --transaction-id txn_019cd444a6d765890d021717a39bf977 # Your agents and customer relationships natural agents list natural customers list # An inbound payment request by id natural payment-requests get --payment-request-id prq_019cd1798d637a4da75dce386343931d ``` ## Flags that work on every command | Flag | What it does | | ----------------- | --------------------------------------------------------------------------------------------------------------------------- | | `--format` | Output format: `json`, `table`, `yaml`, `csv`, `raw`, `jsonl`, `http`. Defaults to `table` in a terminal, `json` when piped | | `--query` | Filter output with a [JMESPath](https://jmespath.org) expression | | `--params` | Merge additional request parameters as JSON (overrides individual flags) | | `--json` | Full JSON request body (`-` reads stdin); replaces the per-field flags | | `--base-url` | Override the API base URL (or set `NATURAL_BASE_URL`) | | `--dry-run` | Validate the request locally without sending it to the API | | `--debug` | Dump the full HTTP request and response to stderr | | `-q, --quiet` | Suppress stdout output on success | | `--version`, `-V` | Print the CLI version | The API key comes from the `NATURAL_API_KEY` environment variable or your stored OAuth login; there is no key flag. ## Related * [SDKs](/guides/platform/sdks): Python and TypeScript client libraries * [MCP](/guides/platform/mcp): Connect Claude, Cursor, and other AI hosts to Natural * [API reference](/api-reference): Field-level detail for every endpoint # Dashboard Source: https://docs.natural.com/guides/platform/dashboard Monitor and control your agents, wallets, and payments The Natural [dashboard](https://www.natural.com/dashboard) is your view of everything on your account and your control surface for what your agents can do. Agents act through the API and MCP; the dashboard is where you watch what they did and set what they're allowed to do. The sidebar has two groups: the main group (Home, Inbox, Agents, Wallets, Transactions, Developers, and Settings) and **Products** (Connect, plus Cards, Accept, and Voice, marked "Soon"). * **Home** — Your balance over time, your agents, and recent transactions, with a **Move money** button to pay, request, deposit, withdraw, or transfer. * **Inbox** — Everything waiting on you: incoming payment requests, approvals, and invitations, with a badge for pending items. * **Agents** — A card per agent; open one to manage its permissions, limits, and credentials. See [Agents](/guides/concepts/agents). * **Wallets** — Hold funds across multiple wallets, attach agents to control what each can spend, and keep money out of agents' reach in the vault. * **Transactions** — The full ledger of payments sent and received; click any row for a shareable receipt. * **Developers** — Create API keys and manage webhooks for the [REST API](/api-reference/about) and [SDKs](/guides/platform/sdks). * **Settings** — Your account details, controls, and notifications. * **Connect** — Manage the customers your agents transact on behalf of, and invite new ones. ## Related * [Start here](/guides/overview/start-here): Set up Natural and connect your first agent * [MCP](/guides/platform/mcp): Connect MCP-aware agents like Claude and ChatGPT * [REST API](/api-reference/about): Direct HTTP access to the Natural API * [SDKs](/guides/platform/sdks): Python and TypeScript client libraries * [CLI](/guides/platform/cli): Drive Natural from the terminal # MCP Source: https://docs.natural.com/guides/platform/mcp Connect AI hosts to Natural's hosted MCP server Natural runs a hosted Model Context Protocol server at **`https://mcp.natural.com`**. OAuth-capable hosts such as Claude, Claude Code, ChatGPT, Codex, and Cursor connect with browser OAuth. No API key to create or paste. Use MCP when an AI host (Claude, Cursor, etc.) runs the agent for you. Building your own agent runtime? Use the [SDKs](/guides/platform/sdks) or [CLI](/guides/platform/cli) instead. ## Connect your host [Sign up](https://natural.com/signup) and complete onboarding first; OAuth signs the host into your Natural account. Pick your tool: Custom connectors require a paid Claude plan (Pro, Max, Team, or Enterprise). The setup is the same for [claude.ai](https://claude.ai) and the Claude Desktop app. 1. In [claude.ai](https://claude.ai) or the Claude Desktop app, open the sidebar, select **Customize**, and go to the **Connectors** tab. 2. Select **Add custom connector** and fill in the fields: name `Natural`, remote MCP server URL `https://mcp.natural.com`. 3. Select **Add**, then **Connect**. Your browser opens Natural's authorization page; approve it, and you are redirected back to Claude. 4. Try it in chat: ```text theme={null} Use Natural to check my wallet balance. ``` Custom plugins require a paid ChatGPT plan (Plus, Pro, Business, or Enterprise). 1. Open **Settings → Security** and turn on **Developer mode**. 2. Select **Plugins** in the sidebar, then the **+** button, and fill in the fields: name `Natural`, MCP server URL `https://mcp.natural.com`, authentication **OAuth**. 3. Accept ChatGPT's unverified-server disclaimer and select **Create**, then **Sign in with Natural**. Approve Natural's authorization page when it opens. 4. Try it in chat: ```text theme={null} Use Natural to check my wallet balance. ``` 1. Add Natural's hosted MCP server and sign in with OAuth. Your browser opens Natural's authorization page; approve it: ```bash theme={null} claude mcp add --transport http natural https://mcp.natural.com --scope user && claude mcp login natural ``` 2. Start a fresh Claude Code session and run `/mcp`. Confirm `natural` shows as connected and authenticated. If it's not, run `claude mcp login natural` again to re-authorize. 3. Try it: ```text theme={null} Use Natural to check my wallet balance. ``` 1. Add Natural as a remote HTTP server, then approve Natural's authorization page in your browser. If the browser does not open on its own, run `codex mcp login natural`: ```bash theme={null} codex mcp add natural --url https://mcp.natural.com ``` 2. Start a fresh Codex session and run `/mcp`. Confirm `natural` shows as connected and authenticated. If it's not, run `codex mcp login natural` again to re-authorize. 3. Try it: ```text theme={null} Use Natural to check my wallet balance. ``` 1. Open the Codex app and go to **Plugins** in the sidebar. 2. Open the dropdown (⌄) in the top right, select **Add marketplace**, and paste this into the **Source** field: ```text theme={null} naturalpay/agent-plugins ``` 3. Switch to **Personal** and select **Install**. Your browser opens Natural's authorization page; approve it. 4. Try it in chat: ```text theme={null} Use Natural to check my wallet balance. ``` 1. In Cursor, open **Settings → Tools & MCPs**, select **New MCP Server** (this opens `mcp.json`), merge Natural into the config, and save: ```json theme={null} { "mcpServers": { "natural": { "url": "https://mcp.natural.com" } } } ``` 2. Find **Natural** under **Settings → Tools & MCPs** and connect it. Your browser opens Natural's authorization page; approve it, and you are redirected back to Cursor. If Natural doesn't appear, restart Cursor. 3. Try it: ```text theme={null} Use Natural to check my wallet balance. ``` For headless or custom agents that can't sign in with browser OAuth: create an [agent key](/guides/concepts/agent-keys) and use it as the bearer token against `mcp.natural.com` or the [REST API](/api-reference/about). See the [API-key fallback](/guides/platform/mcp#api-key-fallback) for details. ### Any other MCP-aware host Most hosts have a UI action, command, or config file for adding a remote MCP server and signing in. Point it at `https://mcp.natural.com` with **no `Authorization` header** so it uses OAuth. For hosts that take `mcpServers` JSON: ```json theme={null} { "mcpServers": { "natural": { "url": "https://mcp.natural.com" } } } ``` * Use `https://mcp.natural.com`; use `https://mcp.natural.com/mcp` only for legacy clients that require an explicit endpoint path. * Set the auth mode to OAuth if the host has one; a bare URL entry defaults to no auth on many hosts and silently skips sign-in. * After signing in, verify by listing Natural tools or calling a read-only tool like `get_account_balance`. If tools are missing, reload the host once and check again. ### CLI OAuth fallback If a host can run terminal commands but has no MCP OAuth flow, sign in with the Natural CLI before reaching for an API key: ```bash theme={null} curl -fsSL https://natural.com/install.sh | bash natural login natural status ``` `natural login` authenticates locally with OAuth, then the CLI's full command surface is available. ### API-key fallback Use a key only when neither hosted MCP OAuth nor CLI OAuth works: headless CI, SDK/REST integrations, or non-interactive scripts. Pass it as the bearer token: * **API key** (`sk_ntl_…`) — Tool calls act as your party. * **Agent key** (`ak_ntl_…`) — Tool calls act as the bound agent, verified by the credential. Keep keys out of chat, source control, and committed config. ```json theme={null} { "mcpServers": { "natural": { "url": "https://mcp.natural.com", "headers": { "Authorization": "Bearer " } } } } ``` ## User-scoped vs agent-scoped OAuth Natural's consent screen determines who the connection acts as: * **As an agent (agent-scoped)** — The default: pick one of your existing agents or create a new one during approval. Every tool call then acts as that agent with a verified binding, and audit records both the agent and the authorizing user. Agent-scoped connections can't call `create_agent` or manage keys. * **As me (user-scoped)** — Tool calls are your own user/party actions, with no agent attribution needed. A consent screen with no agent selection grants user-scoped access. To switch modes, disconnect Natural in the host and reconnect with the other selection. ## What you can do The connector exposes 24 tools, each shaped around an intent. Each one handles the orchestration for you (finding the right wallet, detecting the payer type, auto-selecting a single account), so the agent just says what it wants instead of chaining calls. | Tool | Purpose | | ---------------------------------------------- | ---------------------------------------------------------------------------------- | | `get_transaction_status` | Look up a single payment or transfer by id (`pay_*` or `trf_*`) | | `wait_for_transaction` | Block until a payment/transfer reaches a terminal status (event-driven; max 60s) | | `get_payment_request` | Look up a payment request by id (`prq_*`) | | `list_transactions` | Paginated transaction history | | `get_account_balance` | Wallet balance (available plus pending claims) | | `list_wallets` | Wallet IDs, names, status, default flags, and balances for user/party callers | | `get_identity` | Caller party, acting agent when present, handles, and credential permissions | | `get_party_limits` | Per-transaction, daily, and monthly spend limits for the party (read-only) | | `list_external_accounts` | Linked external accounts and provider connection status | | `get_external_account` | One linked external account by id (`eac_*`) | | `create_external_account_from_processor_token` | Link or refresh external accounts from a Plaid processor token | | `create_payment` | Send a payment. Recipient is an email / phone / `@handle` / `pty_*` / `agt_*` | | `cancel_payment` | Cancel a pending-claim outbound payment by id (`pay_*`) before it is claimed | | `request_payment` | Request a payment. Payer is an email / phone / `@handle` / `pty_*` / `agt_*` | | `fulfill_payment_request` | Pay a request after confirming its current amount and currency | | `decline_payment_request` | Decline an incoming request (`prq_*`) without paying it | | `deposit_funds` | ACH pull from a linked account; falls back to push-to-wallet instructions | | `withdraw_funds` | ACH push to a linked account; auto-selects when exactly one is active | | `transfer_between_wallets` | Move funds between two of your own wallets (`wal_*` to `wal_*`); returns `trf_*` | | `list_agents` | Your agents | | `list_customers` | Your customer relationships (`status` is `active`, `pending`, `revoked`, or `all`) | | `create_agent` | Mint a new programmatic actor (your own party only) | | `invite_customer` | Send a customer invitation by email | | `get_funding_options` | Linked bank accounts + ACH push-to-wallet instructions in one view | ### Amounts and currencies MCP payment tools use a decimal amount and a three-letter currency code: ```json theme={null} { "amount": "10.50", "currency": "USD" } ``` Natural preserves the amount you give it: `$5` becomes `"5.00"` and `$5.3` becomes `"5.30"` without changing the value. If an amount cannot be represented exactly (for example, a USD amount with fractions of a cent), the agent should ask which exact amount to send instead of rounding it. The payment tool accepts only exact decimal amounts without currency symbols or commas, and `currency` is always required. In manual approval mode, your host shows the amount, currency, and destination before the payment runs. When paying a payment request, Natural checks its current amount and currency again and stops if either changed. This format applies to MCP. Natural's REST API and SDKs use integer minor units. See [Data formats](/api-reference/formats). ## Attribution for production agents With an **agent-scoped OAuth grant or an agent key**, agent identity is carried by the credential. For money-moving tools, pass `instanceId` every run so each is auditable. With a **user-scoped grant or a party API key**, tool calls are user/party actions and need no attribution fields. Older integrations can still pass `agentId` for claimed attribution; prefer agent-scoped OAuth or agent keys for new agent integrations. ## Test in the sandbox The [sandbox](/api-reference/sandbox/overview) runs its own MCP server at **`https://mcp.sandbox.natural.com`**. Connect it like the production server; it adds sandbox-only tools (`simulate_customer_deposit`, `simulate_customer_invitation_accept`, and friends) so an agent can drive both sides of a flow. See [Sandbox from MCP, CLI, and SDKs](/api-reference/sandbox/surfaces). ## Docs MCP server This documentation runs its own MCP server at **`https://docs.natural.com/mcp`**, separate from the payments server at `mcp.natural.com`. It requires no Natural account or credentials, so an agent can use it before signup or OAuth. Its tools search these docs, read full pages, and pull the exact request shape of any endpoint from the [OpenAPI spec](/api-reference/openapi.json). ```json theme={null} { "mcpServers": { "natural-docs": { "url": "https://docs.natural.com/mcp" } } } ``` Connect it alongside the payments server while integrating; it is read-only and moves no money. ## Troubleshooting * **Missing tools after connecting** — Reload the host's MCP tools or restart the host window after OAuth completes. * **Auth fails after a previous success** — Disconnect Natural in the host, reconnect, and approve the OAuth screen again. * **Tool reports missing account setup** — Finish KYC/KYB, wallet, or linked-bank setup in the Natural dashboard, then reconnect. For support, include the host name, server URL used, approximate timestamp, your Natural email, any request ID, any visible identifier (`txn_*`, `prq_*`, `pay_*`), and the exact error text. ## Related * [Authentication](/api-reference/authentication) — API keys and scopes * [Agents](/guides/concepts/agents) — The autonomous-actor model behind the connector * [SDKs](/guides/platform/sdks) — Python and TypeScript client libraries * [CLI](/guides/platform/cli) — For terminal and CI use # Reliability Source: https://docs.natural.com/guides/platform/reliability How Natural stays available and behaves predictably under failure You rely on Natural to run agents, hold funds in wallets, and move money, so the platform is built to stay available and to behave predictably when something fails. This page covers how we run the service and how the API responds under failure. ## Infrastructure Natural runs on AWS across multiple availability zones and is served through a global CDN. If an instance or an entire availability zone fails, traffic shifts to healthy capacity automatically. ## Data Your data lives in Amazon Aurora PostgreSQL, running with a primary and a replica across availability zones so the database survives the loss of any single zone. It is encrypted at rest with AWS KMS and backed up automatically. For how data is encrypted and protected, see [Security](/guides/overview/security). ## Behavior under failure The API is built so that a failure or a retry never leaves your data in the wrong state, whether you are creating an agent, funding a wallet, or sending a payment. ### Idempotency Any operation whose accidental repetition would matter, like funding a wallet or sending a payment, takes an `Idempotency-Key`. Retrying with the same key never runs the operation twice, and the recorded outcome, success or failure, replays for 48 hours. See [Idempotency](/api-reference/idempotency). ### Retries Transient failures are safe to retry with exponential backoff. A `429` response carries a `Retry-After` header, and `5xx` responses and network timeouts can be retried under the same idempotency key. See [Rate limits](/api-reference/rate-limits). ### Webhooks Events are delivered at least once from a durable queue. Each event is retried up to seven times over roughly a day with jitter, times out after 30 seconds per attempt, and carries a stable `webhook-id` so you can deduplicate. An endpoint that fails five events in a row is disabled until you re-enable it. See [Webhooks](/guides/webhooks-integration). ### Compatibility The Natural API is additive. We add fields and endpoints, but we do not remove or repurpose the ones you already depend on. See [Backwards compatibility](/api-reference/backwards-compatibility). ## Status Live service status is published at [status.natural.com](https://status.natural.com), covering the API, Dashboard, MCP, and Webhooks. Subscribe there to be notified of incidents. ## Related * [Idempotency](/api-reference/idempotency) - Safe retries for mutations * [Webhooks](/guides/webhooks-integration) - Event delivery and verification * [Security](/guides/overview/security) - How we protect data and control access # SDKs Source: https://docs.natural.com/guides/platform/sdks Client libraries for building agents with Natural Natural has official SDKs for building agents, in Python and TypeScript. ## Available tools `pip install naturalpay` - Build agents in Python `npm install @naturalpay/sdk` - Build agents in TypeScript/JavaScript ## Installation ```bash Python theme={null} pip install naturalpay # or uv add naturalpay ``` ```bash TypeScript theme={null} npm install @naturalpay/sdk # or yarn add @naturalpay/sdk ``` ## Quick start ```python Python theme={null} from naturalpay import Natural # Agent key from NATURAL_API_KEY; instance_id identifies this run. client = Natural(instance_id="invoice-run-1234") # Create a payment on behalf of a customer payment = client.payments.create( amount=10000, # cents - $100.00 currency="USD", counterparty={"type": "email", "value": "contractor@example.com"}, description="Invoice #1234", customer_party_id="pty_019cd34e27c179bfbbe6870486b11b67", idempotency_key="pay-invoice-1234", ) print(payment.data.id) print(payment.data.attributes.status) ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; // One client per run: instanceId names the run, and the SDK sends it // as X-Instance-ID on every call this client makes. const client = new Natural({ instanceId: "invoice-run-1234" }); // agent key from NATURAL_API_KEY // Create a payment on behalf of a customer const payment = await client.payments.create({ amount: 10000, // cents - $100.00 currency: "USD", counterparty: { type: "email", value: "contractor@example.com" }, description: "Invoice #1234", customerPartyId: "pty_019cd34e27c179bfbbe6870486b11b67", idempotencyKey: "pay-invoice-1234", }); console.log(payment.data.id); console.log(payment.data.attributes.status); ``` ## Agent authentication Both SDKs accept either credential type in `NATURAL_API_KEY` (see [Authentication](/api-reference/authentication)): * **Agent key** (`ak_ntl_…`) — Bound to one agent. Requests resolve as that agent automatically: do **not** pass an agent ID (a conflicting one is rejected). An instance ID is **required** for money movement (`payments.create`, transfers, payment-request fulfillment) so each agent run is auditable. In Python, pass `instance_id` to the client constructor and build a client per run. In TypeScript, set it on the constructor or per call. * **API key** (`sk_ntl_…`) — Party-scoped. Calls act as your party. Prefer agent keys for new agent integrations. ### With an agent key ```python Python theme={null} # NATURAL_API_KEY=ak_ntl_prod_... # One client per run: instance_id names the run, and the SDK sends it # as X-Instance-ID on every call this client makes. client = Natural(instance_id="vendor-payouts-q1") payment = client.payments.create( counterparty={"type": "email", "value": "vendor@example.com"}, amount=50000, # cents ($500.00) description="Vendor payment", customer_party_id="pty_019cd34e27c27605a92edc2c7d1a5b34", idempotency_key="vendor_payment_001", ) ``` ```typescript TypeScript theme={null} // NATURAL_API_KEY=ak_ntl_prod_... // One client per run: instanceId is required for money movement with an agent key. const client = new Natural({ instanceId: "vendor-payouts-q1" }); const payment = await client.payments.create({ counterparty: { type: "email", value: "vendor@example.com" }, amount: 50000, // cents ($500.00) description: "Vendor payment", customerPartyId: "pty_019cd34e27c27605a92edc2c7d1a5b34", idempotencyKey: "vendor_payment_001", }); ``` ## Available resources Both SDKs provide these resources. Python names them in snake\_case (`payment_requests`), TypeScript in camelCase (`paymentRequests`): | Resource | Description | | ------------------- | ------------------------------------------------------- | | `payments` | Create, list, and cancel payments | | `payment_requests` | Create, fulfill, and decline payment requests | | `wallets` | Balances, wallet management, and agent attachment | | `transfers` | Deposits, withdrawals, and internal transfers | | `transactions` | List transaction history | | `external_accounts` | Linked bank accounts | | `agents` | Create and manage agents | | `customers` | Customer relationships and invitations | | `invitations` | Agent delegation invitations | | `approvals` | Review and act on approval requests | | `parties` | Party profile, limits, handle, and members | | `identity` | Resolve who a credential acts as | | `api_keys` | Create and revoke API keys | | `agent_keys` | Create, rotate, and revoke agent keys | | `webhooks` | Webhook subscriptions | | `events` | Published event history | | `simulations` | Sandbox only: drive the counterparty side of test flows | ## Test in the sandbox Both SDKs run against the [sandbox](/api-reference/sandbox/overview) unchanged: use a sandbox key and the sandbox base URL. The sandbox-only `simulations` resource drives the counterparty side of every flow. ```typescript TypeScript theme={null} // NATURAL_API_KEY=sk_ntl_sandbox_... const client = new Natural({ baseUrl: "https://api.sandbox.natural.com" }); ``` ```python Python theme={null} # NATURAL_API_KEY=sk_ntl_sandbox_... client = Natural(base_url="https://api.sandbox.natural.com") ``` See [Sandbox from MCP, CLI, and SDKs](/api-reference/sandbox/surfaces) for the full simulation surface. ## Related * [MCP](/guides/platform/mcp) — Connect Claude, Cursor, and other AI hosts to Natural * [CLI](/guides/platform/cli) — For terminal and CI use * [Dashboard](/guides/platform/dashboard) — Onboarding and managing your account * [REST API](/api-reference/about) — Direct HTTP access to the Natural API # Accept Source: https://docs.natural.com/guides/products/accept Accept card payments by giving your agents merchant capabilities, all through API Turn your agent into a merchant to accept card payments, all through API. Works for businesses of any size, from new companies to large enterprises. ## Turn your agent into a merchant * **Flexible payment methods**: Accept debit cards, credit cards, bank transfers, and more. * **Fully programmable**: Accept payments via one-time link or fully programmatically. * **Total visibility**: Every payment is observable and fully auditable so you have maximum visibility. # Cards Source: https://docs.natural.com/guides/products/cards Issue debit and charge cards for your agents the moment they're connected Issue debit and charge cards for your agents the moment they’re connected. The access controls, MCCs, and limits you’re already familiar with, but for agents. ## Give your agent a card * **Programmatic issuing**: Create virtual cards and network tokens with API calls via Natural. * **Flexible checkout**: Bring your own browser automation or use Natural for direct checkout. * **PCI compliant**: Let Natural handle all PCI data so that you stay compliant. # Connect Source: https://docs.natural.com/guides/products/connect Build a platform that enables money movement for your customers across all of Natural's products Build a platform that enables money movement for your customers. Grant access to your agents across your customers and give your customers the full suite of Natural products. ## Create your platform * **Full platform capabilities**: Your customers get full access to all Natural products like Wallets, Pay, Accept, and more. * **Fully programmable**: Invite customers and orchestrate money movement all via the API. * **Create dynamic workflows**: Connect multiple agents at a time, each powering one or more workflows. # Pay Source: https://docs.natural.com/guides/products/pay Pay an agent, email, phone number, and more. Natural powers every type of payments workflow Pay an agent, email, phone number, and more. Natural powers every type of payments workflow, handling the orchestration, ledgering, routing, compliance, risk, and disputes. ## Pay anyone for anything * **Start building immediately**: Pay an agent, email, or phone number. Natural handles the onboarding and compliance. * **Safe by design**: Rely on Natural's network to manage risk, flag fraudulent transactions, and handle disputes. * **Built for scale**: Whether your agent makes one transaction or one million, Natural scales with you. # Request Source: https://docs.natural.com/guides/products/request Request a payment from an agent, email, or phone number with a single API call Request from an agent, email, phone number, and more. Natural powers every type of payments workflow, handling the orchestration, ledgering, routing, compliance, risk, and disputes. ## Request money from anyone * **Start building immediately**: Request from an agent, email, or phone number. Natural handles the rest. * **Safe by design**: Rely on Natural's network to manage risk, flag fraudulent transactions, and handle disputes. * **Built for scale**: Whether your agent makes one transaction or one million, Natural scales with you. # Transfer Source: https://docs.natural.com/guides/products/transfer Transfer money between external accounts with automated treasury management for your agents Transfer money between internal and external accounts. Give your agents access to automated treasury management and empower your financial stack. ## Agentic financial management * **Multiple rails**: Natural handles all types of payment rails so you can use what is best for you. * **Fully programmable**: Any connected agent can send, receive, or request funds with an API call. * **Total visibility**: Every transfer is observable and fully auditable so you have maximum visibility. # Vault Source: https://docs.natural.com/guides/products/vault Receive expanded FDIC coverage with a special protected wallet for your financial reserves One-way accounts for agents. Money moves in, never out. Store funds in a Vault to keep them separate from the accounts your agents can access. ## Peace of mind with Vaults * **Expanded FDIC insurance**1: All Natural accounts receive expanded FDIC coverage. * **Stay in control**: Agents are only allowed to deposit into vaults, but can’t move money out. * **Instant transfers**: Move money between your Vault and Wallets instantly for free.

1 Natural is a financial technology company, not an FDIC-insured depository institution. FDIC deposit insurance covers the failure of an insured depository institution. Certain conditions must be satisfied for pass-through FDIC insurance to apply. Deposits are FDIC-insured through Column N.A., Member FDIC, and Column's Sweep Program Network Banks.

# Voice Source: https://docs.natural.com/guides/products/voice Turn a live phone call into a PCI-compliant card charge from a single call transfer Turn a phone call into a PCI-compliant payment. Use Voice to collect card information and other payments over the phone, all from a single call transfer. ## Technology for voice agents * **PCI compliant**: Transfer your call to Natural to keep your system completely out of PCI scope. * **Capture spoken card details**: Natural can handle DTMF and spoken card details. * **Tokenized card credentials**: Natural tokenizes the card information and stores that payment credentials for you. # Wallet Source: https://docs.natural.com/guides/products/wallet Store funds in FDIC-insured accounts to hold, send, and receive funds, all from an API Store funds in FDIC-insured1 wallets for your agent to use. Create a single wallet or spin up multiple to manage finances. All from one unified API. ## Accounts built for agents * **Multiple wallets**: Use one wallet for all your agents, or one for each. You decide. * **Expanded FDIC insurance**1: All Natural accounts receive expanded FDIC coverage. * **Fully programmable**: Create new wallets on top of Natural entirely programmatically.

1 Natural is a financial technology company, not an FDIC-insured depository institution. FDIC deposit insurance covers the failure of an insured depository institution. Certain conditions must be satisfied for pass-through FDIC insurance to apply. Deposits in Wallet accounts are FDIC-insured through Column N.A., Member FDIC, and Column's Sweep Program Network Banks.

# Deposit and withdraw Source: https://docs.natural.com/guides/transfers/deposit-and-withdraw Transfer from an external account. Move money between a linked bank account and your Natural wallet. A deposit pulls funds in, a withdrawal pushes funds out. Both settle asynchronously. Link a bank account in the dashboard first. Natural connects it through Plaid and returns an `eac_` id you pass on every deposit and withdrawal. Working with a customer's bank account instead? See [Link a customer's bank account](/guides/connect/link-customer-bank). ## Deposit into a wallet Pull money from the bank into a wallet with [`POST /transfers/deposit`](/api-reference/transfers/initiate-deposit). Funds land in your default wallet unless you name another. ```python Python theme={null} import uuid from naturalpay import Natural client = Natural() deposit = client.transfers.initiate_deposit( amount=50_000, currency="USD", external_account_id="eac_550e8400e29b41d4a716446655440000", description="Wallet top-up", idempotency_key=str(uuid.uuid4()), ) print(deposit.data.id) ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const deposit = await client.transfers.initiateDeposit({ amount: 50_000, currency: "USD", externalAccountId: "eac_550e8400e29b41d4a716446655440000", description: "Wallet top-up", idempotencyKey: crypto.randomUUID(), }); console.log(deposit.data.id); ``` ```bash CLI theme={null} natural transfers initiateDeposit \ --amount 50000 \ --currency USD \ --external-account-id eac_550e8400e29b41d4a716446655440000 \ --description "Wallet top-up" \ --idempotency-key "$(uuidgen)" ``` ```text MCP theme={null} Deposit $500 into my wallet from my linked bank account. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/transfers/deposit \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "amount": 50000, "currency": "USD", "externalAccountId": "eac_550e8400e29b41d4a716446655440000", "description": "Wallet top-up" } } }' ``` ## Withdraw to a bank account Push money from a wallet to the bank with [`POST /transfers/withdraw`](/api-reference/transfers/initiate-withdrawal). Natural pulls from your default wallet unless you name another, and the wallet needs enough available balance to cover it. ```python Python theme={null} withdrawal = client.transfers.initiate_withdrawal( amount=12_500, currency="USD", external_account_id="eac_550e8400e29b41d4a716446655440000", description="Payout transfer", idempotency_key=str(uuid.uuid4()), ) print(withdrawal.data.id) ``` ```typescript TypeScript theme={null} const withdrawal = await client.transfers.initiateWithdrawal({ amount: 12_500, currency: "USD", externalAccountId: "eac_550e8400e29b41d4a716446655440000", description: "Payout transfer", idempotencyKey: crypto.randomUUID(), }); console.log(withdrawal.data.id); ``` ```bash CLI theme={null} natural transfers initiateWithdrawal \ --amount 12500 \ --currency USD \ --external-account-id eac_550e8400e29b41d4a716446655440000 \ --description "Payout transfer" \ --idempotency-key "$(uuidgen)" ``` ```text MCP theme={null} Withdraw $125 from my wallet to my linked bank account. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/transfers/withdraw \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "amount": 12500, "currency": "USD", "externalAccountId": "eac_550e8400e29b41d4a716446655440000", "description": "Payout transfer" } } }' ``` ## Confirm the balance Deposits and withdrawals settle over a few business days, so the balance change is not immediate. Read the wallet to confirm a deposit landed before you spend against it, or that a withdrawal left. See [Wallets](/guides/wallets/wallets) for how `balance.available` differs from `balance.total`. # Transfer between wallets Source: https://docs.natural.com/guides/transfers/transfer-between-wallets Move money instantly between two wallets ## Move funds A transfer moves money between two wallets in the same party, landing instantly. Create one with [`POST /transfers/internal`](/api-reference/transfers/initiate-internal-transfer). ```python Python theme={null} import uuid from naturalpay import Natural client = Natural() transfer = client.transfers.initiate_internal( amount=5_000, source_wallet_id="wal_550e8400e29b41d4a716446655440000", dest_wallet_id="wal_7c9e6679e29b41d4a716446655440002", description="Sweep to Vault", idempotency_key=str(uuid.uuid4()), ) print(transfer.data.id) ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const transfer = await client.transfers.initiateInternal({ amount: 5_000, sourceWalletId: "wal_550e8400e29b41d4a716446655440000", destWalletId: "wal_7c9e6679e29b41d4a716446655440002", description: "Sweep to Vault", idempotencyKey: crypto.randomUUID(), }); console.log(transfer.data.id); ``` ```bash CLI theme={null} natural transfers initiateInternal \ --amount 5000 \ --source-wallet-id wal_550e8400e29b41d4a716446655440000 \ --dest-wallet-id wal_7c9e6679e29b41d4a716446655440002 \ --description "Sweep to Vault" \ --idempotency-key "$(uuidgen)" ``` ```text MCP theme={null} Move $50 from my operating wallet to my Vault. ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/transfers/internal \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "amount": 5000, "sourceWalletId": "wal_550e8400e29b41d4a716446655440000", "destWalletId": "wal_7c9e6679e29b41d4a716446655440002", "description": "Sweep to Vault" } } }' ``` # Vault Source: https://docs.natural.com/guides/wallets/vault A reserve wallet agents can fund but never spend from The Vault is a reserve wallet that no agent can spend from. It keeps money separate from your day-to-day agent activity. Anyone can move money in, including agents, but no agent can take money out. ## What the Vault is Every wallet has a `walletType` of `standard` or `vault`. A standard wallet is the everyday, spendable one. The Vault is the locked-down reserve. List your wallets with [`GET /wallets`](/api-reference/wallets/list-wallets) to see the field. The Vault sits alongside your standard wallets, set apart only by its `walletType`. ```python Python theme={null} from naturalpay import Natural client = Natural() wallets = client.wallets.list() for wallet in wallets.data: print(wallet.id, wallet.attributes.display_name, wallet.attributes.wallet_type) ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const wallets = await client.wallets.list(); for (const wallet of wallets.data) { console.log(wallet.id, wallet.attributes.displayName, wallet.attributes.walletType); } ``` ```bash CLI theme={null} natural wallets list ``` ```text MCP theme={null} List my Natural wallets and show each one's type and balance. ``` ```bash cURL theme={null} curl https://api.natural.com/wallets \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` ## How the Vault differs The Vault behaves differently from a standard wallet in three ways. **1. You cannot create one through the API.** The Vault is provisioned once after onboarding. **2. Agents cannot be attached to Vault or move money out of it.** Agents can still fund the Vault by transferring in from a standard wallet. **3. It cannot be your default wallet.** Keep a standard wallet as your default so calls that omit `walletId` resolve somewhere spendable. # Wallets Source: https://docs.natural.com/guides/wallets/wallets Create and manage your wallets Wallets holds your money on Natural. You can open multiple to keep funds separate: payroll separate from operating cash, or a dedicated pool per agent. ## Create a wallet Create a new wallet with [`POST /wallets`](/api-reference/wallets/create-wallet). ```python Python theme={null} import uuid from naturalpay import Natural client = Natural() wallet = client.wallets.create( idempotency_key=str(uuid.uuid4()), display_name="Payroll", description="Dedicated wallet for payroll runs", ) print(wallet.data.id) ``` ```typescript TypeScript theme={null} import Natural from "@naturalpay/sdk"; const client = new Natural(); const wallet = await client.wallets.create({ idempotencyKey: crypto.randomUUID(), displayName: "Payroll", description: "Dedicated wallet for payroll runs", }); console.log(wallet.data.id); ``` ```bash CLI theme={null} natural wallets create \ --idempotency-key "$(uuidgen)" \ --display-name "Payroll" \ --description "Dedicated wallet for payroll runs" ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/wallets \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "displayName": "Payroll", "description": "Dedicated wallet for payroll runs" } } }' ``` The API only creates `standard` wallets. The **Vault** is set up automatically, never through the API. See [Vault](/guides/wallets/vault). A new wallet starts empty. Fund it by [depositing from a linked bank account](/guides/transfers/deposit-and-withdraw), or [transfer money in from another of your wallets](/guides/transfers/transfer-between-wallets). ## Check a balance List all walletes in one call to [`GET /wallets`](/api-reference/wallets/list-wallets) and get details with [`GET /wallets/{walletId}`](/api-reference/wallets/get-wallet). ```python Python theme={null} wallets = client.wallets.list() for wallet in wallets.data: balance = wallet.attributes.balance available = balance.available if balance else 0 print(wallet.id, wallet.attributes.display_name, available) ``` ```typescript TypeScript theme={null} const wallets = await client.wallets.list(); for (const wallet of wallets.data) { console.log(wallet.id, wallet.attributes.displayName, wallet.attributes.balance?.available ?? 0); } ``` ```bash CLI theme={null} natural wallets list ``` ```text MCP theme={null} What is my wallet balance? ``` ```bash cURL theme={null} curl https://api.natural.com/wallets \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` Each wallet carries two figures: available and total. available is what you can spend now, with total including pending transfers and holds. Generally you should always opt for using available balance. To read one wallet, pass its id to [`GET /wallets/{walletId}`](/api-reference/wallets/get-wallet): ```python Python theme={null} wallet = client.wallets.get("wal_550e8400e29b41d4a716446655440000") balance = wallet.data.attributes.balance print(balance.available if balance else 0) ``` ```typescript TypeScript theme={null} const wallet = await client.wallets.get({ walletId: "wal_550e8400e29b41d4a716446655440000", }); console.log(wallet.data.attributes.balance?.available ?? 0); ``` ```bash CLI theme={null} natural wallets get --wallet-id wal_550e8400e29b41d4a716446655440000 ``` ```bash cURL theme={null} curl https://api.natural.com/wallets/wal_550e8400e29b41d4a716446655440000 \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` ## Rename a wallet Rename a wallet or change its description with [`PATCH /wallets/{walletId}`](/api-reference/wallets/update-wallet). ```python Python theme={null} wallet = client.wallets.update( "wal_550e8400e29b41d4a716446655440000", idempotency_key=str(uuid.uuid4()), display_name="Operating (US)", description="Primary USD operating wallet", ) ``` ```typescript TypeScript theme={null} const wallet = await client.wallets.update({ walletId: "wal_550e8400e29b41d4a716446655440000", idempotencyKey: crypto.randomUUID(), displayName: "Operating (US)", description: "Primary USD operating wallet", }); ``` ```bash CLI theme={null} natural wallets update \ --wallet-id wal_550e8400e29b41d4a716446655440000 \ --idempotency-key "$(uuidgen)" \ --display-name "Operating (US)" \ --description "Primary USD operating wallet" ``` ```bash cURL theme={null} curl -X PATCH https://api.natural.com/wallets/wal_550e8400e29b41d4a716446655440000 \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": { "attributes": { "displayName": "Operating (US)", "description": "Primary USD operating wallet" } } }' ``` ## Set your default Your default wallet is where Natural puts money when a payment, deposit, or withdrawal omits `walletId`, and where payments to your email, phone, or handle land. Set it with [`POST /wallets/{walletId}/default`](/api-reference/wallets/set-default-wallet). The default is a party-level setting: exactly one wallet is your party's default, and setting a new one clears the old. Each agent also has its own default, set separately. See [Wallet access](/guides/controls/wallet-access). ```python Python theme={null} client.wallets.set_default( "wal_550e8400e29b41d4a716446655440000", idempotency_key=str(uuid.uuid4()), ) ``` ```typescript TypeScript theme={null} await client.wallets.setDefault({ walletId: "wal_550e8400e29b41d4a716446655440000", idempotencyKey: crypto.randomUUID(), }); ``` ```bash CLI theme={null} natural wallets setDefault \ --wallet-id wal_550e8400e29b41d4a716446655440000 \ --idempotency-key "$(uuidgen)" ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/wallets/wal_550e8400e29b41d4a716446655440000/default \ -H "Authorization: Bearer $NATURAL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` The Vault cannot be your default. Keep a standard wallet as your default so calls that leave out `walletId` always resolve somewhere spendable. ## Freeze a wallet Freezing with [`POST /wallets/{walletId}/freeze`](/api-reference/wallets/freeze-wallet) stops money moving in or out while leaving the balance intact. Unfreeze with [`POST /wallets/{walletId}/unfreeze`](/api-reference/wallets/unfreeze-wallet) to return the wallet to `active`. ```python Python theme={null} client.wallets.freeze("wal_550e8400e29b41d4a716446655440000") client.wallets.unfreeze("wal_550e8400e29b41d4a716446655440000") ``` ```typescript TypeScript theme={null} await client.wallets.freeze({ walletId: "wal_550e8400e29b41d4a716446655440000" }); await client.wallets.unfreeze({ walletId: "wal_550e8400e29b41d4a716446655440000" }); ``` ```bash CLI theme={null} natural wallets freeze --wallet-id wal_550e8400e29b41d4a716446655440000 natural wallets unfreeze --wallet-id wal_550e8400e29b41d4a716446655440000 ``` ```bash cURL theme={null} curl -X POST https://api.natural.com/wallets/wal_550e8400e29b41d4a716446655440000/freeze \ -H "Authorization: Bearer $NATURAL_API_KEY" curl -X POST https://api.natural.com/wallets/wal_550e8400e29b41d4a716446655440000/unfreeze \ -H "Authorization: Bearer $NATURAL_API_KEY" ``` You cannot freeze your party's default wallet. Make another wallet the default first.