---
url: /docs/public/prompts/quickstart-payin-autonomous.md
---
# Build a BlindPay payin 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 payin on a development instance — a sender pays fiat over bank rails and BlindPay delivers the equivalent stablecoins into a managed wallet.

## Settled decisions — do not reopen

ACH as the payment method. $100.00 (`request_amount: 10000`, minor units). A managed wallet on `base_sepolia` as the destination. `USDB`, the development test token. Sender covers fees. Zero questions until the script exists.

## 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 come only from the process environment or an env file in the project root — never from sibling or parent directories — and are never printed, logged, or committed. They are requested only at the Phase 2 handoff.

## Phase 1: Write the script

Write one self-contained script, `blindpay-payin.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` — exit with a one-line error if 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-payin" }`. 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": "base_sepolia", "name": "Quickstart Wallet" }`. Keep the wallet ID (`bl_...`).
4. **Payin quote** — `POST /instances/{instance_id}/payin-quotes` with `{ "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 on the quote ID (parts of the docs say `pq_` — accept either). Keep the ID.
5. **Payin** — `POST /instances/{instance_id}/payins/evm` (this endpoint serves every network) with `{ "payin_quote_id": "<quote id>" }` immediately after quoting; payin quotes expire in 5 minutes. Expect a `pi_` prefix. Print `memo_code` and the `blindpay_bank_details` fields (routing number, account number, beneficiary) as a labeled list, not raw JSON. On development the memo code is the literal string `<development>`.
6. **Verify** — `GET /instances/{instance_id}/customers/{customer_id}/wallets/{wallet_id}/balance`. 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 (`request_amount: 10000` minor units settles as `amount: 100`). On development the deposit settles within about 15–30 seconds of the payin: poll every 15 seconds, up to 8 times, until `USDB.amount` is nonzero. If the final poll still reads zero, print the raw response body before reporting failure — a misread settled balance must not be reported as a failed payin.

## Phase 2: The handoff

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

```
The payin 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 and BLINDPAY_INSTANCE_ID
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), and finish with the bank details to pay into
   plus the settled wallet balance.

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

If both 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 balance lands, report: the IDs created (customer, wallet, payin), the bank details and memo code, the final balance — 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: Virtual accounts (https://blindpay.com/docs/virtual-accounts), Blockchain wallets (https://blindpay.com/docs/blockchain-wallets), the Payout quickstart (https://blindpay.com/docs/quickstart-payout), and Webhooks (https://blindpay.com/docs/learn/webhooks).

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