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

# Make Your First Payment API Call

> Authenticate to Lead, create an entity, assign an account number, simulate an incoming ACH credit, and send an outgoing ACH in sandbox.

### Before You Start

You will need:

* **Sandbox API credentials:** A `client_id` and `client_secret` provided by your Lead Technical Account Manager during kickoff.
* **curl:** Installed on macOS by default; `curl --version` should print something. Any HTTP client works.
* **Optional:** jq— for cleaner JSON output. Pipe responses through `| jq .` if you have it installed.

<Note>
  **Sandbox Only**

  Every example uses [api.sandbox.lead.bank](http://api.sandbox.lead.bank). The same calls work against [api.lead.bank](http://api.lead.bank) in production once your program is approved for go-live, but you should never run this guide against production credentials.
</Note>

## Steps

<Badge color="blue" size="lg" shape="pill" icon="watch" iconType="regular">15 minutes</Badge>

We'll go through a simple flow to authenticate against Lead, create an entity, assign an account number, simulate an incoming ACH credit, and send an outgoing ACH.

<AccordionGroup>
  <Accordion title="1: Get an Access Token">
    Lead uses OAuth2 client credentials. You exchange your `client_id` and `client_secret` for a short-lived access token (24-hour expiry by default) and pass that token as a Bearer header on every subsequent request.

    Replace `YOUR_CLIENT_ID` and `YOUR_CLIENT_SECRET` with the values from your Technical Account Manager.

    ```text theme={null}
    curl -X POST https://api.sandbox.lead.bank/v1/oauth/token \
      -H 'Content-Type: application/json' \
      -d '{
        "grant_type": "client_credentials",
        "client_id": "YOUR_CLIENT_ID",
        "client_secret": "YOUR_CLIENT_SECRET",
        "scope": "entity/read_write ach/read_write account_number/read_write"
      }'
    ```

    You should see a response like this:

    ```text theme={null}
    {
      "access_token": "eyJhbGciOiJSUzI1NiIs...",
      "token_type": "Bearer",
      "scope": "entity/read_write ach/read_write account_number/read_write",
      "expires_in": 86400
    }
    ```

    Save the `access_token` value as a shell variable so the next steps are easy to copy:

    ```text theme={null}
    export LEAD_TOKEN="eyJhbGciOiJSUzI1NiIs..."
    ```

    <Note>
      **Scopes**

      Access tokens are scoped. Request only the scopes you need — and you can request a narrower set later for production.
    </Note>
  </Accordion>

  <Accordion title="2: Create an Individual Entity">
    Every transaction at Lead starts with creating a representation for your customer in Lead's system. For illustrative purposes, the request below creates an example Individual Entity with minimal information provided.

    ```text theme={null}
    curl -X POST https://api.sandbox.lead.bank/v0/entities \
      -H "Authorization: Bearer $LEAD_TOKEN" \
      -H 'Content-Type: application/json' \
      -H 'Idempotency-Key: quickstart-entity-001' \
      -d '{
        "type": "individual",
        "client_customer_id": "customer-id-from-my-system",
        "intended_roles": ["account_holder"],
        "risk_score": "low",
        "individual_details": {
          "first_name": "Ada",
          "last_name": "Lovelace",
          "date_of_birth": "1990-12-10",
          "identification_details": {
            "type": "us_entity",
            "tax_identification": { "type": "ssn", "value": "123456789" }
          },
          "contact_methods": [
            { "type": "email", "email": "ada@example.com" }
          ],
          "address_details": {
            "physical_address": {
              "line_1": "123 Analytical Engine Way",
              "city": "Brooklyn",
              "state": "NY",
              "postal_code": "11201",
              "country": "US"
            }
          }
        },
        "kyc_details": { "result": "passed", "screened_at": "2026-01-15T10:00:00Z" },
        "ofac_details": { "result": "passed", "screened_at": "2026-01-15T10:00:00Z" }
      }'
    ```

    A successful response includes an `id` that starts with `entity_.` Save it for the next step:

    ```text theme={null}
    export LEAD_ENTITY_ID="entity_abc123..."
    ```

    <Note>
      **Idempotency**

      Every write endpoint requires an Idempotency-Key header. If you retry the same request with the same key (e.g. after a network blip), Lead returns the original response instead of creating a duplicate. Use a fresh key per logical operation.
    </Note>
  </Accordion>

  <Accordion title="3: Retrieve the Entity">
    Confirm the entity is queryable. You can fetch by Lead’s entity ID you supplied.

    ```text theme={null}
    curl https://api.sandbox.lead.bank/v0/entities/$LEAD_ENTITY_ID \
      -H "Authorization: Bearer $LEAD_TOKEN"
    ```

    You should get back the same entity object you just created, with timestamps populated.
  </Accordion>

  <Accordion title="4: Create an Account Number">
    Before you can receive or send money, you need an Account Number assigned to your account. Lead provides an account\_id during onboarding — this is your FBO account, not a per-user account. Use it directly to create an Account Number for each customer.

    **Create the account number:**

    ```text theme={null}
    curl -X POST https://api.sandbox.lead.bank/v1/account_number \
      -H "Authorization: Bearer $LEAD_TOKEN" \
      -H 'Content-Type: application/json' \
      -H 'Idempotency-Key: quickstart-acct-num-001' \
      -d '{
        "account_id": "YOUR_ACCOUNT_ID"
      }'
    ```

    Replace `YOUR_ACCOUNT_ID` with the `account_id` provided by your Technical Account Manager.

    A successful response includes an id that starts with account\_number\_xyz and the 10-digit DDA number alongside Lead's routing number. Save the ID — you'll use it in the next two steps:

    ```text theme={null}
    export LEAD_ACCT_NUM_ID="account_number_xyz..."
    ```

    <Note>
      **Account Numbers and Routing**

      Lead assigns a real account number and routing number to every Account Number object. This is what counterparties use to send money to your customer. Multiple Account Numbers can map to a single Account — for example, one for ACH and one for wire.
    </Note>
  </Accordion>

  <Accordion title="5: Simulate an Incoming ACH Credit">
    The sandbox provides simulation endpoints that are not available in production. They let you trigger inbound transactions so you can test the full lifecycle of a payment. The example below is for ACH, but wires, and instant payments are similar.

    ```text theme={null}
    curl -X POST https://api.sandbox.lead.bank/v1/simulate/ach/incoming_ach \
      -H "Authorization: Bearer $LEAD_TOKEN" \
      -H 'Content-Type: application/json' \
      -H 'Idempotency-Key: quickstart-incoming-ach-001' \
      -d '{
        "account_number_id": "'$LEAD_ACCT_NUM_ID'",
        "amount": 12500,
        "currency_code": "USD",
        "transaction_type": "credit",
        "sec_code": "PPD",
        "statement_descriptor": "DEPOSIT",
        "sender_name": "Quickstart Co",
        "sender_company_id": "1234567890",
        "sender_routing_number": 121000248
      }'
    ```

    The response includes an id that starts with `ach_`. Copy it and substitute it for `{ach_id}` in the retrieve call below. Amounts are in cents — 12500 is \$125.00.

    ```text theme={null}
    curl https://api.sandbox.lead.bank/v1/ach/{ach_id} \
      -H "Authorization: Bearer $LEAD_TOKEN"
    ```

    <Note>
      **Sandbox**

      ACH transactions don't advance through their lifecycle on real network timing in sandbox. Use `POST /v1/simulate/ach/{ach_id}/advance` to move the transfer through its states manually.
    </Note>
  </Accordion>

  <Accordion title="6: Send an Outgoing ACH">
    Now send money out from your account. An outgoing ACH requires an Account Number on the sending side and a destination routing number and account number on the receiving side.

    ```text theme={null}
    curl -X POST https://api.sandbox.lead.bank/v1/ach \
      -H "Authorization: Bearer $LEAD_TOKEN" \
      -H 'Content-Type: application/json' \
      -H 'Idempotency-Key: quickstart-outgoing-ach-001' \
      -d '{
        "account_number_id": "'$LEAD_ACCT_NUM_ID'",
        "amount": 5000,
        "currency_code": "USD",
        "transaction_type": "debit",
        "delivery_type": "next_business_day",
        "sec_code": "PPD",
        "statement_descriptor": "PAYMENT",
        "counterparty": {
          "name": "Jordan Smith",
          "routing_number": "021000021",
          "account_number": "9900000001",
          "account_type": "checking"
        }
      }'
    ```

    A successful response returns an id that starts with `ach_` and an initial status of `created`. Save it:

    ```text theme={null}
    export LEAD_ACH_ID="ach_abc123..."
    ```

    Retrieve the transfer to confirm its status:

    ```text theme={null}
    curl https://api.sandbox.lead.bank/v1/ach/{id} \
      -H "Authorization: Bearer $LEAD_TOKEN"
    ```

    <Note>
      **Effective Entry Date**

      Setting `delivery_type` to `next_business_day` schedules the ACH for the next valid settlement date. Use `same_business_day` for same-day ACH if your program supports it. Lead derives the effective date automatically — see [*ACH → Processing Windows*](/api-integrations/ach/processing-windows) for cutoff times and valid date rules.
    </Note>

    <Note>
      **Sandbox**

      Outgoing ACH transfers don't submit to the real network. Use `POST /v1/simulate/ach/{ach_id}/advance` to move the transfer through its lifecycle states manually.
    </Note>
  </Accordion>
</AccordionGroup>

## What's Next

* **[Webhooks](/api-integrations/webhooks):** Receive events instead of polling. Configure your endpoint and start listening for `ach.created`, `ach.posted`, and lifecycle events for every other object.
* **[Idempotency & Errors:](/api-integrations/idempotency)** Error envelope, retry semantics, and the canonical error codes you’ll handle.
* [**Move Money Guides:**](/api-integrations/ach/overview) Full per-rail walkthroughs for ACH, Wire, Instant Payments, Blockchain, and Internal Transfers.

## Troubleshooting

| <h4>Error</h4>                                | <h4>Potential Fix</h4>                                                                                                                                                                                                                                                                                                                |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **401 Unauthorized**                          | <ul><li>Token expired (24h default). Run Step 1 again.</li><li>Authorization header malformed. Should be exactly `Authorization: Bearer <token>` — note the space and capitalization.</li></ul>                                                                                                                                       |
| **403 source\_ip\_not\_allowed**              | <ul><li>Your client’s public IP isn’t on the program allowlist.</li><li>Either add your office or VPN egress IP via your <Tooltip tip="Technical Account Manager">TAM</Tooltip>, or run the request from an allowlisted environment.</li><li>See [Auth & Security → IP Allowlist](/api-integrations/overview#ip-allowlist).</li></ul> |
| **422 Unprocessable Entity on Create Entity** | <ul><li>Most common cause: missing or malformed `kyc_details / ofac_details`. Sandbox accepts placeholder values, but they must be present and structurally valid.</li><li>Second most common: `intended_roles` mismatched with entity type. For `business` or `sole_prop`, only `account_holder` is valid.</li></ul>                 |
| **409 Conflict**                              | <ul><li>You reused an Idempotency-Key with a different request body.</li><li>Either send the same body (and you’ll get the original response back), or pick a new key.</li></ul>                                                                                                                                                      |
