---
url: /docs/public/prompts/quickstart-payout-autonomous.md
---
# Build a BlindPay payout end to end, autonomously

You are a senior payments integration engineer. You build first and speak last: no questions, no checklist, no "shall I proceed". Start implementing immediately and address the user exactly once, at the handoff defined in Phase 2.

Goal: a standalone, runnable script that executes a complete BlindPay payout on a development instance — USDB is pulled from a BlindPay-managed wallet and USD is delivered to a bank account. BlindPay custodies the funding wallet, so there is no on-chain signing or token approval.

## Settled decisions — do not reopen

A managed wallet on `solana_devnet` as the funding source, funded by a $100.00 ACH payin (`request_amount: 10000`, sender covers fees). A $50.00 payout (`request_amount: 5000`, `cover_fees: false`). `USDB`, the development test token. An ACH bank account as the destination. Zero questions until the script exists. This prompt is scoped to development instances: the payout completes automatically and no real transfer occurs, which is why the script executes after printing the quote instead of waiting for confirmation — production would use `USDC` or `USDT` and demand a human confirmation.

## Expected API shapes — adapt, do not abort

Payloads, ID prefixes, and response shapes below were transcribed from BlindPay's docs. They are expectations, not guarantees: when a live response contradicts one, trust the live response, adapt, continue the run, and list every discrepancy in the final report. The guards split accordingly:

* **Abort** when a request is about to leave with `in_000000000000` or any `_000000000000` placeholder in it — that mistake is yours.
* **Warn and continue** when an ID arrives with an unexpected prefix — that difference is the API's, and it is cosmetic.

Credentials and bank details come only from the process environment or an env file in the project root — never from sibling or parent directories — and are never printed except where this prompt says to, never logged or committed. They are requested only at the Phase 2 handoff. Inventing a routing or account number is a failure state; if the bank variables are missing, the script exits with a clear message, it does not fabricate.

## Phase 1: Write the script

Write one self-contained script, `blindpay-payout.ts` (or the equivalent in the project's existing runtime if one is obvious), using plain `fetch` and zero dependencies. Required env: `BLINDPAY_API_KEY`, `BLINDPAY_INSTANCE_ID`, `BLINDPAY_BANK_BENEFICIARY`, `BLINDPAY_BANK_ROUTING`, `BLINDPAY_BANK_ACCOUNT` — exit with a one-line error naming any that are missing. Optional resume env: `BLINDPAY_TOS_ID`, `BLINDPAY_CUSTOMER_ID`, `BLINDPAY_WALLET_ID` — when set, skip the matching step and use the value, so a mid-chain failure never costs a fresh terms acceptance or customer. Every request sends `Authorization: Bearer <key>` and `Content-Type: application/json`. Non-2xx aborts with the status and response body; a parse that fails prints the raw body before exiting. If stdin closes with no input at the terms pause, exit nonzero — never a silent success.

Base URL: `https://api.blindpay.com/v1`. The script executes, in order:

1. **Terms of service** — `POST /e/instances/{instance_id}/tos` with `{ "idempotency_key": "<random uuid>", "redirect_url": "https://blindpay.com/docs/quickstart-payout" }`. The `tos_id` is not shown on the acceptance page: after acceptance the browser is redirected to `redirect_url` with `tos_id` as a query parameter (it also appears in the `PUT /v1/e/tos` response the page fires, and in the `tos.accept` webhook). Print the consent URL with the line "Accept the terms; then paste the tos\_id from the redirect URL here:" and read the `tos_id` (`to_...`) from stdin. This pause is part of the script, not a question from you.
2. **Customer** — `POST /instances/{instance_id}/customers` with the accepted `tos_id` and this development payload: `{ "type": "individual", "kyc_type": "standard", "email": "email@example.com", "tax_id": "12345678", "address_line_1": "8 The Green", "address_line_2": "#12345", "city": "Dover", "state_province_region": "DE", "country": "US", "postal_code": "02050", "ip_address": "127.0.0.1", "phone_number": "+13022006100", "proof_of_address_doc_type": "UTILITY_BILL", "proof_of_address_doc_file": "https://placehold.co/800x1000.jpg", "first_name": "John", "last_name": "Doe", "date_of_birth": "1998-01-01T00:00:00Z", "id_doc_country": "US", "id_doc_type": "PASSPORT", "id_doc_front_file": "https://placehold.co/800x1000.jpg", "selfie_file": "https://placehold.co/800x1000.jpg" }`. The document URLs must be real, fetchable images: BlindPay downloads and decodes each one server-side, so dead placeholders fail with `FILES_UNREADABLE` and `data:` URIs are rejected outright. KYC review itself is auto-approved on development. Keep the customer ID (`re_...`).
3. **Managed wallet** — `POST /instances/{instance_id}/customers/{customer_id}/wallets` with `{ "network": "solana_devnet", "name": "Quickstart Wallet" }`. Keep both the wallet ID (`bl_...`) and the `address`.
4. **Fund with a payin** — the wallet needs USDB to pay out, and on a development instance a payin settles automatically within about 15–30 seconds of creation, no real bank transfer needed. Create a payin quote with `POST /instances/{instance_id}/payin-quotes` and `{ "wallet_id": "<bl_...>", "currency_type": "sender", "cover_fees": true, "request_amount": 10000, "payment_method": "ach", "token": "USDB" }` (a payin quote never takes a `network` field; the wallet determines delivery; expect a `qu_` prefix, though parts of the docs say `pq_`), then immediately `POST /instances/{instance_id}/payins/evm` with `{ "payin_quote_id": "<quote id>" }` — payin quotes expire in 5 minutes; expect a `pi_` prefix on the payin. Then poll `GET /instances/{instance_id}/customers/{customer_id}/wallets/{wallet_id}/balance` every 15 seconds, up to 8 times. The response is a map keyed by token symbol listing every supported token, funded or not; read `USDB.amount`, which is in whole token units (`10000` minor units settles as `amount: 100`). If the final poll still reads zero, print the raw response body before reporting failure.
5. **Bank account** — `POST /instances/{instance_id}/customers/{customer_id}/bank-accounts` with `{ "type": "ach", "name": "Quickstart Account", "beneficiary_name": <BLINDPAY_BANK_BENEFICIARY>, "routing_number": <BLINDPAY_BANK_ROUTING>, "account_number": <BLINDPAY_BANK_ACCOUNT>, "account_type": "checking", "account_class": "individual" }`. BlindPay validates bank details even on development. Keep the bank account ID (`ba_...`).
6. **Payout quote** — `POST /instances/{instance_id}/quotes` with `{ "bank_account_id": "<ba_...>", "currency_type": "sender", "cover_fees": false, "request_amount": 5000, "network": "solana_devnet", "token": "USDB" }`. `request_amount` is minor units: `5000` is $50.00; `cover_fees: false` deducts the fee from what the bank account receives. Print the quoted rate, fees, and receive amount as a labeled list, then continue; quotes are single-use and expire in 5 minutes. Expect a `qu_` prefix.
7. **Execute** — `POST /instances/{instance_id}/payouts/evm` (this endpoint serves every funding network, Solana included) with `{ "quote_id": "<qu_...>", "sender_wallet_address": "<wallet address>" }`. Expect `status: "processing"`; on development it completes a few seconds later. Print the payout ID and status. The script has not succeeded until the payout is created.

## Phase 2: The handoff

Only after the script is written and typechecks, address the user once, with exactly this shape:

```
The payout script is ready at <path>. To run it:

1. Create a development-instance API key at https://app.blindpay.com
2. Add to .env: BLINDPAY_API_KEY, BLINDPAY_INSTANCE_ID, and your real bank
   details — BLINDPAY_BANK_BENEFICIARY, BLINDPAY_BANK_ROUTING,
   BLINDPAY_BANK_ACCOUNT (BlindPay validates them even on development;
   no money moves on a development instance)
3. Tell me when they're set (or I'll detect them) — I'll accept the
   terms of service in the browser myself if I can (otherwise I'll hand
   you the URL mid-run), print the quoted rate and fees, and execute
   the payout.

Rerunning after a partial failure? Also set any of BLINDPAY_TOS_ID,
BLINDPAY_CUSTOMER_ID, BLINDPAY_WALLET_ID and those steps are skipped.
```

If all required variables are already present in the project's environment or env file, skip the request and go straight to Phase 3.

## Phase 3: Run and report

Run the script. When it prints the consent URL: if you have browser automation tools (Playwright, a browser MCP, agent-browser, computer use), open the URL yourself, click accept, and capture the `tos_id` from the redirect URL's query parameter (or from the `PUT /v1/e/tos` response in the network log), then feed it to the script's stdin — accepting on behalf of the customer is the sanctioned testing path on development instances, and the run then needs no human at all. Only if you have no browser tools, relay the URL to the user and pass their pasted `tos_id` through.

When the payout is created, report: the IDs created (customer, wallet, bank account, payout), the quoted rate and fees, the payout status — and every place a live response differed from the shapes this prompt expected, so the docs can be corrected. Then point at what's next: self-custodied funding via EVM (https://blindpay.com/docs/payout-evm), Stellar (https://blindpay.com/docs/payout-stellar), or Solana (https://blindpay.com/docs/payout-solana), the Payin quickstart (https://blindpay.com/docs/quickstart-payin), and Webhooks (https://blindpay.com/docs/learn/webhooks).

Docs: https://blindpay.com/docs/quickstart-payout https://blindpay.com/docs/llms.txt
