> ## Documentation Index
> Fetch the complete documentation index at: https://docs.natural.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> When building application code, use `@naturalpay/sdk` for TypeScript or `naturalpay` for Python.
> When searching Natural documentation, use the docs MCP at `https://docs.natural.com/mcp`.
> Use the Natural CLI for terminal and CI automation.
> 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.

# Pay over a specific rail

> Send ACH, realtime, or wire payments to people and businesses not on Natural, for yourself or for a customer

<Note>
  Direct is in early access and subject to change. To request Direct access, contact [hi@natural.com](mailto:hi@natural.com).
</Note>

Use Direct to pay a person or business that isn't on Natural, straight to their bank.

1. Save the payee once as an [external party](/guides/concepts/external-parties).
2. Add their bank account.
3. Send over [ACH](/guides/concepts/ach), [realtime](/guides/concepts/realtime), or [wire](/guides/concepts/wire).

To pay by email, phone, or handle instead, see [Send a payment](/guides/payments/send-payment).

<Snippet file="shared/prerequisites.mdx" />

<Snippet file="shared/cents-note.mdx" />

## Choose a rail

| Rail | Destination | Settles | Reversible after settlement |
| - | - | - | - |
| [ACH](/guides/concepts/ach) | External party account | Network batches, 1–3 days | Yes, the bank can return it |
| [Realtime](/guides/concepts/realtime) | External party account | Seconds, over RTP or FedNow | No |
| [Wire](/guides/concepts/wire) | External party account | Subject to bank processing | No |

## Create the external party

Create the payee with [`POST /external-parties`](/api-reference/external-parties/create-external-party). `kind` (`individual` or `business`) and `name` are required; an individual's `name` needs a first and last name. Include a complete `address` if you will send wires.

<CodeGroup>
  ```python Python theme={null}
  import uuid
  from naturalpay import Natural

  client = Natural()

  party = client.external_parties.create(
      kind="business",
      name="Harbor Freight Logistics LLC",
      email="ap@harborfreight.example",
      address={
          "line1": "500 Terry Francois Blvd",
          "city": "San Francisco",
          "state": "CA",
          "postal_code": "94158",
          "country_code": "US",
      },
      idempotency_key=str(uuid.uuid4()),
  )
  print(party.data.id, party.data.attributes.screening_status)
  ```

  ```typescript TypeScript theme={null}
  import Natural from "@naturalpay/sdk";

  const client = new Natural();
  const party = await client.externalParties.create({
    kind: "business",
    name: "Harbor Freight Logistics LLC",
    email: "ap@harborfreight.example",
    address: {
      line1: "500 Terry Francois Blvd",
      city: "San Francisco",
      state: "CA",
      postalCode: "94158",
      countryCode: "US",
    },
    idempotencyKey: crypto.randomUUID(),
  });
  console.log(party.data.id, party.data.attributes.screeningStatus);
  ```

  ```bash CLI theme={null}
  natural external-parties create \
    --idempotency-key "$(uuidgen)" \
    --json '{
      "kind": "business",
      "name": "Harbor Freight Logistics LLC",
      "email": "ap@harborfreight.example",
      "address": {
        "line1": "500 Terry Francois Blvd",
        "city": "San Francisco",
        "state": "CA",
        "postalCode": "94158",
        "countryCode": "US"
      }
    }'
  ```

  ```text MCP theme={null}
  Add Harbor Freight Logistics LLC as a business payee, ap@harborfreight.example,
  500 Terry Francois Blvd, San Francisco, CA 94158.
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.natural.com/external-parties \
    -H "Authorization: Bearer $NATURAL_API_KEY" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{
      "data": {
        "attributes": {
          "kind": "business",
          "name": "Harbor Freight Logistics LLC",
          "email": "ap@harborfreight.example",
          "address": {
            "line1": "500 Terry Francois Blvd",
            "city": "San Francisco",
            "state": "CA",
            "postalCode": "94158",
            "countryCode": "US"
          }
        }
      }
    }'
  ```
</CodeGroup>

<Snippet file="api-examples/externalParties.create.response.mdx" />

Natural screens every external party and reports the result in `screeningStatus`: `clear`, `under_review`, or `rejected`. A `rejected` party cannot have bank accounts stored or be paid.

## Store the bank account

Store the payee's US bank account with [`POST /external-party-accounts`](/api-reference/external-party-accounts/store-banking-details-for-an-external-party). The response never returns the full numbers, only `last4` and a mask.

<CodeGroup>
  ```python Python theme={null}
  account = client.external_party_accounts.create(
      external_party_id="epty_019cd1798d8d6873421613705e3c1415",
      account_details={
          "type": "us_bank_account",
          "account_type": "checking",
          "account_number": "123456786789",
          "routing_number": "121000358",
      },
      idempotency_key=str(uuid.uuid4()),
  )
  print(account.data.id, account.data.attributes.supported_rails)
  ```

  ```typescript TypeScript theme={null}
  const account = await client.externalPartyAccounts.create({
    externalPartyId: "epty_019cd1798d8d6873421613705e3c1415",
    accountDetails: {
      type: "us_bank_account",
      accountType: "checking",
      accountNumber: "123456786789",
      routingNumber: "121000358",
    },
    idempotencyKey: crypto.randomUUID(),
  });
  console.log(account.data.id, account.data.attributes.supportedRails);
  ```

  ```bash CLI theme={null}
  natural external-party-accounts create \
    --idempotency-key "$(uuidgen)" \
    --json '{
      "externalPartyId": "epty_019cd1798d8d6873421613705e3c1415",
      "accountDetails": {
        "type": "us_bank_account",
        "accountType": "checking",
        "accountNumber": "123456786789",
        "routingNumber": "121000358"
      }
    }'
  ```

  ```text MCP theme={null}
  Save a checking account for Harbor Freight Logistics: routing 121000358, account 123456786789.
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.natural.com/external-party-accounts \
    -H "Authorization: Bearer $NATURAL_API_KEY" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{
      "data": {
        "attributes": {
          "externalPartyId": "epty_019cd1798d8d6873421613705e3c1415",
          "accountDetails": {
            "type": "us_bank_account",
            "accountType": "checking",
            "accountNumber": "123456786789",
            "routingNumber": "121000358"
          }
        }
      }
    }'
  ```
</CodeGroup>

<Snippet file="api-examples/externalPartyAccounts.create.response.mdx" />

* `supportedRails` lists the rails the account is expected to accept (`ach`, `realtime`, `wire`). It is advisory; the authoritative check runs when you create the payment.
* `validationState` starts `validated` or `pending`. A `pending` account resolves asynchronously and fires `external_party_account.validated` when it validates. ACH credits work while it is `pending`. A `failed` account cannot be paid; store a new one.

Store the account once and reuse its `epa_*` for every later payment to that payee. List a payee's saved accounts with [`GET /external-party-accounts?externalPartyId=epty_...`](/api-reference/external-party-accounts/list-external-party-accounts).

## Send the payment

Every Direct create takes an `Idempotency-Key`. Retry a timed-out create with the same key and body; a reused key with a different body returns `409 idempotency_key_reused`. The payment draws from your default wallet; pass `walletId` (`wal_*`) to spend from a specific wallet, and an agent with access to more than one wallet must pass it.

<Snippet file="shared/instance-id-note.mdx" />

<Tabs>
  <Tab title="ACH">
    [`POST /ach`](/api-reference/ach/create-ach-payment) with `direction: "credit"`. `companyEntryDescription` (up to 10 characters, uppercased) appears on the payee's bank statement. Optional `addenda` (up to 80 characters) carries remittance data to the receiving bank.

    <CodeGroup>
      ```python Python theme={null}
      import uuid
      from naturalpay import Natural

      # NATURAL_API_KEY=ak_ntl_prod_... (the agent's key)
      agent_client = Natural(
          instance_id=str(uuid.uuid4()),
      )

      ach = agent_client.ach.create(
          direction="credit",
          amount=25_000,
          external_party_account_id="epa_019cd1798d8e5a2f8ceb96020cc3991a",
          company_entry_description="PAYOUT",
          addenda="Invoice 1042, July services",
          idempotency_key=str(uuid.uuid4()),
      )
      print(ach.data.id, ach.data.attributes.status)
      ```

      ```typescript TypeScript theme={null}
      // NATURAL_API_KEY=ak_ntl_prod_... (the agent's key)
      const ach = await client.ach.create(
        {
          direction: "credit",
          amount: 25_000,
          externalPartyAccountId: "epa_019cd1798d8e5a2f8ceb96020cc3991a",
          companyEntryDescription: "PAYOUT",
          addenda: "Invoice 1042, July services",
          idempotencyKey: crypto.randomUUID(),
        },
        {
          instanceId: crypto.randomUUID(),
        },
      );
      console.log(ach.data.id, ach.data.attributes.status);
      ```

      ```bash CLI theme={null}
      # NATURAL_API_KEY=ak_ntl_prod_... (the agent's key)
      natural ach create \
        --x-instance-id "$(uuidgen)" \
        --idempotency-key "$(uuidgen)" \
        --json '{
          "direction": "credit",
          "amount": 25000,
          "externalPartyAccountId": "epa_019cd1798d8e5a2f8ceb96020cc3991a",
          "companyEntryDescription": "PAYOUT",
          "addenda": "Invoice 1042, July services"
        }'
      ```

      ```text MCP theme={null}
      Using my Procurement Agent, send Harbor Freight Logistics $250 by ACH for invoice 1042.
      ```

      ```bash cURL theme={null}
      # NATURAL_API_KEY=ak_ntl_prod_... (the agent's key)
      curl -X POST https://api.natural.com/ach \
        -H "Authorization: Bearer $NATURAL_API_KEY" \
        -H "X-Instance-ID: $(uuidgen)" \
        -H "Idempotency-Key: $(uuidgen)" \
        -H "Content-Type: application/json" \
        -d '{
          "data": {
            "attributes": {
              "direction": "credit",
              "amount": 25000,
              "externalPartyAccountId": "epa_019cd1798d8e5a2f8ceb96020cc3991a",
              "companyEntryDescription": "PAYOUT",
              "addenda": "Invoice 1042, July services"
            }
          }
        }'
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Realtime">
    [`POST /realtime`](/api-reference/realtime/create-realtime-payment). `description` (1–140 characters, ISO 20022 character set) is delivered to the payee's bank. A network rejection arrives within seconds as `FAILED`; Natural does not fall back to another rail, so create an ACH payment to reach the account anyway.

    <CodeGroup>
      ```python Python theme={null}
      realtime = agent_client.realtime.create(
          amount=25_000,
          external_party_account_id="epa_019cd1798d8e5a2f8ceb96020cc3991a",
          description="Invoice 1042 payout",
          idempotency_key=str(uuid.uuid4()),
      )
      ```

      ```typescript TypeScript theme={null}
      const realtime = await client.realtime.create(
        {
          amount: 25_000,
          externalPartyAccountId: "epa_019cd1798d8e5a2f8ceb96020cc3991a",
          description: "Invoice 1042 payout",
          idempotencyKey: crypto.randomUUID(),
        },
        { instanceId: crypto.randomUUID() },
      );
      ```

      ```bash CLI theme={null}
      natural realtime create \
        --x-instance-id "$(uuidgen)" \
        --idempotency-key "$(uuidgen)" \
        --json '{
          "amount": 25000,
          "externalPartyAccountId": "epa_019cd1798d8e5a2f8ceb96020cc3991a",
          "description": "Invoice 1042 payout"
        }'
      ```

      ```text MCP theme={null}
      Using my Procurement Agent, send Harbor Freight Logistics $250 by realtime payment for invoice 1042.
      ```

      ```bash cURL theme={null}
      curl -X POST https://api.natural.com/realtime \
        -H "Authorization: Bearer $NATURAL_API_KEY" \
        -H "X-Instance-ID: $(uuidgen)" \
        -H "Idempotency-Key: $(uuidgen)" \
        -H "Content-Type: application/json" \
        -d '{
          "data": {
            "attributes": {
              "amount": 25000,
              "externalPartyAccountId": "epa_019cd1798d8e5a2f8ceb96020cc3991a",
              "description": "Invoice 1042 payout"
            }
          }
        }'
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Wire">
    [`POST /wire`](/api-reference/wire/create-wire-payment). `description` is your internal label and is not sent to the bank; `remittanceInfo` (up to 105 characters) is what the beneficiary sees. Fedwire needs the external party's complete address.

    <CodeGroup>
      ```python Python theme={null}
      wire = agent_client.wire.create(
          amount=250_000,
          external_party_account_id="epa_019cd1798d8e5a2f8ceb96020cc3991a",
          description="Q3 vendor settlement",
          remittance_info="INV 1042",
          idempotency_key=str(uuid.uuid4()),
      )
      ```

      ```typescript TypeScript theme={null}
      const wire = await client.wire.create(
        {
          amount: 250_000,
          externalPartyAccountId: "epa_019cd1798d8e5a2f8ceb96020cc3991a",
          description: "Q3 vendor settlement",
          remittanceInfo: "INV 1042",
          idempotencyKey: crypto.randomUUID(),
        },
        { instanceId: crypto.randomUUID() },
      );
      ```

      ```bash CLI theme={null}
      natural wire create \
        --x-instance-id "$(uuidgen)" \
        --idempotency-key "$(uuidgen)" \
        --json '{
          "amount": 250000,
          "externalPartyAccountId": "epa_019cd1798d8e5a2f8ceb96020cc3991a",
          "description": "Q3 vendor settlement",
          "remittanceInfo": "INV 1042"
        }'
      ```

      ```text MCP theme={null}
      Using my Procurement Agent, wire Harbor Freight Logistics $2,500 for the Q3 settlement, reference INV 1042.
      ```

      ```bash cURL theme={null}
      curl -X POST https://api.natural.com/wire \
        -H "Authorization: Bearer $NATURAL_API_KEY" \
        -H "X-Instance-ID: $(uuidgen)" \
        -H "Idempotency-Key: $(uuidgen)" \
        -H "Content-Type: application/json" \
        -d '{
          "data": {
            "attributes": {
              "amount": 250000,
              "externalPartyAccountId": "epa_019cd1798d8e5a2f8ceb96020cc3991a",
              "description": "Q3 vendor settlement",
              "remittanceInfo": "INV 1042"
            }
          }
        }'
      ```
    </CodeGroup>

    If the party has no complete address, create returns `400 wire.beneficiary_address_required`. Correct it with [`PATCH /external-parties/{externalPartyId}/address`](/api-reference/external-parties/correct-an-external-party-address), passing the party's current `addressVersion` as `expectedAddressVersion`, then retry the wire with the same idempotency key.
  </Tab>
</Tabs>

The response carries the payment ID and its initial `status`. An ACH payment looks like this:

<Snippet file="api-examples/ach.create.response.mdx" />

A payment that breaches a [limit](/guides/concepts/limits) starts at `AWAITING_APPROVAL` and moves on once it is [approved](/guides/controls/approvals). Common create errors:

| Error | What to do |
| - | - |
| `wallet.insufficient_balance` | Fund the wallet, then retry with a new idempotency key |
| `external_party.screening_not_clear` | Wait for screening to clear; a `rejected` party cannot be paid |
| `party.descriptor_name_unavailable` | Set a legal name, or a first and last name, on the sending party |
| `wire.beneficiary_address_required` | Correct the external party's address, then retry |
| `ach.not_enabled`, `realtime.not_enabled`, and so on | The rail is not enabled for the sending party; contact [hi@natural.com](mailto:hi@natural.com) |

Each rail's full list is on its errors page: [ACH](/api-reference/errors/ach), [realtime](/api-reference/errors/realtime), [wire](/api-reference/errors/wire).

## Track the payment

Read one payment with its rail's get endpoint, for example [`GET /ach/{achId}`](/api-reference/ach/get-ach-payment), or list them with filters such as `status` and `externalPartyId`. Direct payments also appear in [`GET /transactions`](/api-reference/transactions/list-transactions) alongside the rest of your money movement.

<CodeGroup>
  ```python Python theme={null}
  ach = client.ach.get("ach_019cd1798d838f575681880edfaa4c6f")
  print(ach.data.attributes.status, ach.data.attributes.fee)

  for payment in client.ach.list(external_party_id="epty_019cd1798d8d6873421613705e3c1415"):
      print(payment.id, payment.attributes.status)
  ```

  ```typescript TypeScript theme={null}
  const ach = await client.ach.get({ achId: "ach_019cd1798d838f575681880edfaa4c6f" });
  console.log(ach.data.attributes.status, ach.data.attributes.fee);

  const page = await client.ach.list({ externalPartyId: "epty_019cd1798d8d6873421613705e3c1415" });
  for await (const payment of page) {
    console.log(payment.id, payment.attributes.status);
  }
  ```

  ```bash CLI theme={null}
  natural ach get --ach-id ach_019cd1798d838f575681880edfaa4c6f
  natural ach list --external-party-id epty_019cd1798d8d6873421613705e3c1415
  ```

  ```text MCP theme={null}
  What's the status of ach_019cd1798d838f575681880edfaa4c6f?
  ```

  ```bash cURL theme={null}
  curl https://api.natural.com/ach/ach_019cd1798d838f575681880edfaa4c6f \
    -H "Authorization: Bearer $NATURAL_API_KEY"
  ```
</CodeGroup>

Every rail starts at `CREATED` and moves to `PROCESSING` once submitted. The terminal states differ by rail:

| Rail | Success | Other terminal states |
| - | - | - |
| ACH | `SETTLED` | `RETURNED` (even after `SETTLED`), `FAILED`, `CANCELED`, `APPROVAL_DENIED` |
| Realtime | `SETTLED` | `FAILED`, `CANCELED`, `APPROVAL_DENIED` |
| Wire | `COMPLETED` | `REJECTED`, `FAILED`, `CANCELED`, `APPROVAL_DENIED` |

The fee is assessed at submission, so `fee` is `null` until the payment reaches `PROCESSING`. Rather than polling, subscribe to the rail's [webhooks](/guides/webhooks/receive-events): `ach.*`, `realtime.*`, and `wire.*`. Approval holds emit `approval.*`. Payloads are in the [event catalog](/api-reference/event-catalog).

## Cancel a payment

ACH, realtime, and wire payments can be canceled only before submission, while they are `CREATED` or `AWAITING_APPROVAL`. Cancel takes a `reason` and an `Idempotency-Key`; once submission starts it returns `400 <rail>.not_cancellable`.

<CodeGroup>
  ```python Python theme={null}
  agent_client.ach.cancel(
      "ach_019cd1798d838f575681880edfaa4c6f",
      reason="Duplicate invoice",
      idempotency_key=str(uuid.uuid4()),
  )
  ```

  ```typescript TypeScript theme={null}
  await client.ach.cancel(
    {
      achId: "ach_019cd1798d838f575681880edfaa4c6f",
      reason: "Duplicate invoice",
      idempotencyKey: crypto.randomUUID(),
    },
    { instanceId: crypto.randomUUID() },
  );
  ```

  ```bash CLI theme={null}
  natural ach cancel \
    --ach-id ach_019cd1798d838f575681880edfaa4c6f \
    --x-instance-id "$(uuidgen)" \
    --idempotency-key "$(uuidgen)" \
    --json '{ "reason": "Duplicate invoice" }'
  ```

  ```text MCP theme={null}
  Cancel ach_019cd1798d838f575681880edfaa4c6f, it's a duplicate invoice.
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.natural.com/ach/ach_019cd1798d838f575681880edfaa4c6f/cancel \
    -H "Authorization: Bearer $NATURAL_API_KEY" \
    -H "X-Instance-ID: $(uuidgen)" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{ "data": { "attributes": { "reason": "Duplicate invoice" } } }'
  ```
</CodeGroup>

## Test in the sandbox

Sandbox Direct payments don't reach a real bank. Drive them to a terminal state with simulations:

| Rail | Simulations |
| - | - |
| Realtime | [Settle](/api-reference/simulations/settle-realtime-payment) · [Fail](/api-reference/simulations/fail-realtime-payment) |
| Wire | [Complete](/api-reference/simulations/settle-wire) · [Fail](/api-reference/simulations/fail-wire) |

<CodeGroup>
  ```python Python theme={null}
  client.simulations.settle_realtime(
      "rt_019cd1798d84b1c26a34c5e19f4a0f7c",
      idempotency_key=str(uuid.uuid4()),
  )
  ```

  ```typescript TypeScript theme={null}
  await client.simulations.settleRealtime({
    realtimeId: "rt_019cd1798d84b1c26a34c5e19f4a0f7c",
    idempotencyKey: crypto.randomUUID(),
  });
  ```

  ```bash CLI theme={null}
  natural simulations settle-realtime \
    --realtime-id rt_019cd1798d84b1c26a34c5e19f4a0f7c \
    --idempotency-key "$(uuidgen)"
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.sandbox.natural.com/simulations/realtime/rt_019cd1798d84b1c26a34c5e19f4a0f7c/settle \
    -H "Authorization: Bearer $NATURAL_API_KEY" \
    -H "Idempotency-Key: $(uuidgen)"
  ```
</CodeGroup>

## Initiate payment for a customer

An agent connected to a customer can pay that customer's vendors and contractors from the customer's wallet. The flow is the same as above with one field added, `customerPartyId`, on every call: in `data.attributes` for creates and cancels, and as a query parameter for reads. For the connection model, read the [Customers overview](/guides/concepts/customers).

### Ask for Direct permissions

Direct needs its own grants on the customer connection. Request them when you [invite the customer](/guides/connect/invite-customer).

| Permission | Allows |
| - | - |
| `external_parties.create` | Create the customer's payees and store their bank accounts |
| `external_parties.read` | Read the customer's payees and accounts |
| `direct.create` | Send and cancel ACH, realtime, and wire payments from customer wallets |
| `direct.read` | Read the customer's Direct payments and receive their webhooks |

`direct.create` implies `direct.read`, and `external_parties.create` implies `external_parties.read`. `direct.create` can be granted only to an agent, so Direct calls for a customer authenticate with the agent's key and name the run with `X-Instance-ID`.

```json theme={null}
{
  "data": {
    "attributes": {
      "recipients": [{ "type": "email", "value": "ops@bistroroma.com" }],
      "agents": [
        {
          "agentId": "agt_019cd1798d637a4da75dce386343931d",
          "permissions": [
            "external_parties.create",
            "external_parties.read",
            "direct.create",
            "direct.read",
            "wallets.balance"
          ],
          "limits": { "perTransaction": 500000 }
        }
      ]
    }
  }
}
```

A customer connected before you asked for Direct must approve a new invitation that includes these permissions. Each rail must also be enabled on the **customer's** account, not just yours. If it isn't, create returns `403 ach.not_enabled` (or `realtime.not_enabled`, `wire.not_enabled`).

### Create the payee under the customer

Your customers' party IDs (`pty_*`) are on [`GET /customers`](/api-reference/customers/list-customers); see [Manage access](/guides/connect/manage-access). External parties and their accounts belong to one party. Create them with `customerPartyId` so they belong to the customer. A payee you created for your own party can't be paid from a customer's wallet.

<CodeGroup>
  ```python Python theme={null}
  import uuid
  from naturalpay import Natural

  # NATURAL_API_KEY=ak_ntl_prod_... (the agent's key)
  agent_client = Natural(
      instance_id=str(uuid.uuid4()),
  )

  party = agent_client.external_parties.create(
      customer_party_id="pty_019cd1798d627ad9bc302511c4f2c115",
      kind="business",
      name="Golden Gate Produce Co",
      email="billing@ggproduce.example",
      idempotency_key=str(uuid.uuid4()),
  )

  account = agent_client.external_party_accounts.create(
      customer_party_id="pty_019cd1798d627ad9bc302511c4f2c115",
      external_party_id=party.data.id,
      account_details={
          "type": "us_bank_account",
          "account_type": "checking",
          "account_number": "123456786789",
          "routing_number": "121000358",
      },
      idempotency_key=str(uuid.uuid4()),
  )
  ```

  ```typescript TypeScript theme={null}
  import Natural from "@naturalpay/sdk";

  // NATURAL_API_KEY=ak_ntl_prod_... (the agent's key)
  const client = new Natural();
  const instanceId = crypto.randomUUID();

  const party = await client.externalParties.create(
    {
      customerPartyId: "pty_019cd1798d627ad9bc302511c4f2c115",
      kind: "business",
      name: "Golden Gate Produce Co",
      email: "billing@ggproduce.example",
      idempotencyKey: crypto.randomUUID(),
    },
    { instanceId },
  );

  const account = await client.externalPartyAccounts.create(
    {
      customerPartyId: "pty_019cd1798d627ad9bc302511c4f2c115",
      externalPartyId: party.data.id,
      accountDetails: {
        type: "us_bank_account",
        accountType: "checking",
        accountNumber: "123456786789",
        routingNumber: "121000358",
      },
      idempotencyKey: crypto.randomUUID(),
    },
    { instanceId },
  );
  ```

  ```bash CLI theme={null}
  # NATURAL_API_KEY=ak_ntl_prod_... (the agent's key)
  natural external-parties create \
    --x-instance-id "$(uuidgen)" \
    --idempotency-key "$(uuidgen)" \
    --json '{
      "customerPartyId": "pty_019cd1798d627ad9bc302511c4f2c115",
      "kind": "business",
      "name": "Golden Gate Produce Co",
      "email": "billing@ggproduce.example"
    }'

  natural external-party-accounts create \
    --x-instance-id "$(uuidgen)" \
    --idempotency-key "$(uuidgen)" \
    --json '{
      "customerPartyId": "pty_019cd1798d627ad9bc302511c4f2c115",
      "externalPartyId": "epty_019cd1798d8d6873421613705e3c1415",
      "accountDetails": {
        "type": "us_bank_account",
        "accountType": "checking",
        "accountNumber": "123456786789",
        "routingNumber": "121000358"
      }
    }'
  ```

  ```text MCP theme={null}
  Using my Procurement Agent, add Golden Gate Produce Co as a payee for Bistro Roma, billing@ggproduce.example,
  and save their checking account: routing 121000358, account 123456786789.
  ```

  ```bash cURL theme={null}
  # NATURAL_API_KEY=ak_ntl_prod_... (the agent's key)
  curl -X POST https://api.natural.com/external-parties \
    -H "Authorization: Bearer $NATURAL_API_KEY" \
    -H "X-Instance-ID: $(uuidgen)" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{
      "data": {
        "attributes": {
          "customerPartyId": "pty_019cd1798d627ad9bc302511c4f2c115",
          "kind": "business",
          "name": "Golden Gate Produce Co",
          "email": "billing@ggproduce.example"
        }
      }
    }'

  curl -X POST https://api.natural.com/external-party-accounts \
    -H "Authorization: Bearer $NATURAL_API_KEY" \
    -H "X-Instance-ID: $(uuidgen)" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{
      "data": {
        "attributes": {
          "customerPartyId": "pty_019cd1798d627ad9bc302511c4f2c115",
          "externalPartyId": "epty_019cd1798d8d6873421613705e3c1415",
          "accountDetails": {
            "type": "us_bank_account",
            "accountType": "checking",
            "accountNumber": "123456786789",
            "routingNumber": "121000358"
          }
        }
      }
    }'
  ```
</CodeGroup>

To reuse payees the customer already has, list them with [`GET /external-parties?customerPartyId=pty_...`](/api-reference/external-parties/list-external-parties) and [`GET /external-party-accounts?customerPartyId=pty_...&externalPartyId=epty_...`](/api-reference/external-party-accounts/list-external-party-accounts).

### Pay from the customer's wallet

Add `customerPartyId` to the rail's create. The payment draws from the customer's default wallet, or the customer wallet you name in `walletId`, and counts against the limits on your connection. ACH is shown; [`POST /realtime`](/api-reference/realtime/create-realtime-payment) and [`POST /wire`](/api-reference/wire/create-wire-payment) take the same field.

<CodeGroup>
  ```python Python theme={null}
  ach = agent_client.ach.create(
      customer_party_id="pty_019cd1798d627ad9bc302511c4f2c115",
      direction="credit",
      amount=180_000,
      external_party_account_id="epa_019cd1798d8e5a2f8ceb96020cc3991a",
      company_entry_description="PRODUCE",
      addenda="Invoice 3301, week of Oct 5",
      idempotency_key=str(uuid.uuid4()),
  )
  ```

  ```typescript TypeScript theme={null}
  const ach = await client.ach.create(
    {
      customerPartyId: "pty_019cd1798d627ad9bc302511c4f2c115",
      direction: "credit",
      amount: 180_000,
      externalPartyAccountId: "epa_019cd1798d8e5a2f8ceb96020cc3991a",
      companyEntryDescription: "PRODUCE",
      addenda: "Invoice 3301, week of Oct 5",
      idempotencyKey: crypto.randomUUID(),
    },
    { instanceId },
  );
  ```

  ```bash CLI theme={null}
  natural ach create \
    --x-instance-id "$(uuidgen)" \
    --idempotency-key "$(uuidgen)" \
    --json '{
      "customerPartyId": "pty_019cd1798d627ad9bc302511c4f2c115",
      "direction": "credit",
      "amount": 180000,
      "externalPartyAccountId": "epa_019cd1798d8e5a2f8ceb96020cc3991a",
      "companyEntryDescription": "PRODUCE",
      "addenda": "Invoice 3301, week of Oct 5"
    }'
  ```

  ```text MCP theme={null}
  Using my Procurement Agent, pay Golden Gate Produce Co $1,800 by ACH from Bistro Roma's wallet for invoice 3301.
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.natural.com/ach \
    -H "Authorization: Bearer $NATURAL_API_KEY" \
    -H "X-Instance-ID: $(uuidgen)" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{
      "data": {
        "attributes": {
          "customerPartyId": "pty_019cd1798d627ad9bc302511c4f2c115",
          "direction": "credit",
          "amount": 180000,
          "externalPartyAccountId": "epa_019cd1798d8e5a2f8ceb96020cc3991a",
          "companyEntryDescription": "PRODUCE",
          "addenda": "Invoice 3301, week of Oct 5"
        }
      }
    }'
  ```
</CodeGroup>

The response's `customerParty` relationship is the customer and `wallet` is the customer's wallet, confirming the funds came from them. Abridged:

```json theme={null}
{
  "data": {
    "type": "ach",
    "id": "ach_019cd1798d838f575681880edfaa4c6f",
    "attributes": {
      "direction": "credit",
      "amount": 180000,
      "status": "PROCESSING",
      "submittedDescriptor": {
        "companyName": "Bistro Roma LLC",
        "companyEntryDescription": "PRODUCE"
      }
    },
    "relationships": {
      "customerParty": {
        "data": { "type": "party", "id": "pty_019cd1798d627ad9bc302511c4f2c115" }
      },
      "wallet": {
        "data": { "type": "wallet", "id": "wal_019cd1798d714ce765e0486a417c23dc" }
      }
    }
  }
}
```

* **The customer is the sender.** The payee's bank statement shows the customer's legal name as the ACH company name (`submittedDescriptor.companyName`), not yours. The customer needs a legal name, or a first and last name, or create returns `400 party.descriptor_name_unavailable`.
* **Approvals.** A payment over your connection's limits starts at `AWAITING_APPROVAL` and waits for the customer to [approve](/guides/controls/approvals) it.
* **Access is rechecked.** Natural checks the connection again before submitting a payment. If the customer revoked it or removed `direct.create` in the meantime, the payment is not sent.

### Track and cancel for the customer

Pass `customerPartyId` as a query parameter on reads and in `data.attributes` on cancel. Without it, reads look in your own party and return `404` for the customer's payments.

<CodeGroup>
  ```python Python theme={null}
  ach = agent_client.ach.get(
      "ach_019cd1798d838f575681880edfaa4c6f",
      customer_party_id="pty_019cd1798d627ad9bc302511c4f2c115",
  )

  agent_client.ach.cancel(
      "ach_019cd1798d838f575681880edfaa4c6f",
      customer_party_id="pty_019cd1798d627ad9bc302511c4f2c115",
      reason="Invoice disputed",
      idempotency_key=str(uuid.uuid4()),
  )
  ```

  ```typescript TypeScript theme={null}
  const ach = await client.ach.get({
    achId: "ach_019cd1798d838f575681880edfaa4c6f",
    customerPartyId: "pty_019cd1798d627ad9bc302511c4f2c115",
  });

  await client.ach.cancel(
    {
      achId: "ach_019cd1798d838f575681880edfaa4c6f",
      customerPartyId: "pty_019cd1798d627ad9bc302511c4f2c115",
      reason: "Invoice disputed",
      idempotencyKey: crypto.randomUUID(),
    },
    { instanceId },
  );
  ```

  ```bash CLI theme={null}
  natural ach get \
    --ach-id ach_019cd1798d838f575681880edfaa4c6f \
    --customer-party-id pty_019cd1798d627ad9bc302511c4f2c115

  natural ach cancel \
    --ach-id ach_019cd1798d838f575681880edfaa4c6f \
    --x-instance-id "$(uuidgen)" \
    --idempotency-key "$(uuidgen)" \
    --json '{ "customerPartyId": "pty_019cd1798d627ad9bc302511c4f2c115", "reason": "Invoice disputed" }'
  ```

  ```text MCP theme={null}
  What's the status of Bistro Roma's payment ach_019cd1798d838f575681880edfaa4c6f?
  ```

  ```bash cURL theme={null}
  curl "https://api.natural.com/ach/ach_019cd1798d838f575681880edfaa4c6f?customerPartyId=pty_019cd1798d627ad9bc302511c4f2c115" \
    -H "Authorization: Bearer $NATURAL_API_KEY"

  curl -X POST https://api.natural.com/ach/ach_019cd1798d838f575681880edfaa4c6f/cancel \
    -H "Authorization: Bearer $NATURAL_API_KEY" \
    -H "X-Instance-ID: $(uuidgen)" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{
      "data": {
        "attributes": {
          "customerPartyId": "pty_019cd1798d627ad9bc302511c4f2c115",
          "reason": "Invoice disputed"
        }
      }
    }'
  ```
</CodeGroup>

Lifecycles, cancel windows, and errors are the same as for your own payments; see [Track the payment](#track-the-payment).

### Webhooks

`ach.*`, `realtime.*`, and `wire.*` events for the customer's payments go to your [webhooks](/guides/webhooks/receive-events) that include `"connected"` in `sources`, while your connection holds `direct.read` (implied by `direct.create`). Payloads are in the [event catalog](/api-reference/event-catalog).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.