1. Authenticate
Create an API key in Orangepill Console and send it as Authorization: Bearer <key>. Keys
are scoped to a tenant or a project; x-project-id selects the project.
Developers
The Orangepill v4 API is REST over HTTPS, versioned in the path, and described by an OpenAPI 3.1 contract. Examples on this page are checked against the platform code. This page covers what you need to judge it: authentication, environments, a first operation, lifecycle states, errors, webhooks and the sandbox.
What are you trying to ship?
Accept a bank transfer or local payment method without coupling the product to one provider.
Route payments across providers while keeping one payment lifecycle and API.
Keep customer value, reservations and financial effects tied to ledger state.
Create a payout with an idempotency key, reserve value, follow provider state and confirm a terminal outcome.
Connect collection, conversion or liquidity, and payout while preserving the state of each leg.
Let an agent request financial actions through scoped tools and economic intents instead of provider credentials.
Quickstart
Create an API key in Orangepill Console and send it as Authorization: Bearer <key>. Keys
are scoped to a tenant or a project; x-project-id selects the project.
Production: https://console.orangepill.cloud. Sandbox is a separate deployment with its own
data: https://sandbox.console.orangepill.cloud. Routes live under /v4. Credentials
do not cross environments.
Send the request with an idempotency key you generate. Retrying with the same key returns the same payout instead of sending a second one.
curl -X POST "$ORANGEPILL_API/v4/payouts" \
-H "Authorization: Bearer $ORANGEPILL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"idempotency_key": "payout-2026-10-11-000123",
"product_key": "bank_transfer.bre_b",
"amount": { "value": "1500000", "currency": "COP" },
"recipient": { "scheme": "ALIAS", "value": "<beneficiary alias>" },
"source": { "type": "wallet", "wallet_account_id": "<wallet account id>" }
}'
Amounts are strings in minor units. Send exactly one of product_key (routed: Orangepill selects
the provider) or providerInstanceId (direct). Some rails also require merchant_id.
Response:
{
"success": true,
"data": {
"payoutId": "<uuid>",
"status": "pending",
"reservationId": "<uuid>"
}
}
Poll GET /v4/payouts/{payoutId}. For payments, use /status,
/timeline and /attempts, or subscribe to webhooks.
pending and processing mean the outcome is not final. Do not create a new payout to
"retry" it; ask for the status of the existing one.
A payout is finished at completed, failed or cancelled. Failures carry
errorCode and errorMessage. RETRY_EXHAUSTED means Orangepill stopped
checking without a final provider answer: treat it as unknown.
Core concepts
The outcome your product wants, such as paying a beneficiary or funding a wallet. Over REST, payment intents and payouts are the entry points; agents submit economic intents through MCP tools.
The orchestration of steps, decisions and user interactions around an operation. Flows decide what should happen next; execution moves the value.
One governed operation and the provider attempts made for it. Each attempt keeps its own state, visible through the attempts and timeline endpoints.
Wallet balances derive from a double-entry ledger. A wallet-funded payout records a reservation, returned as reservationId; it does not currently reduce available balance.
The point where an outcome can no longer change. Non-terminal statuses mean the outcome is not final yet; treat them as unknown, not as failure.
Payouts require an idempotency_key, unique per tenant. Payments and refunds accept one. Reusing a key returns the existing operation instead of creating another.
Evidence of how an outcome was produced. Today: settlement Proof of Record tokens, timelines, attempts and the explain endpoint.
Governed cases for operations that cannot safely continue: Investigate → Establish → Decide → Verify.
Agents connect through the Orangepill MCP server with OAuth 2.0 (client credentials or PKCE) and act through scoped tools and economic intents.
Implementation Guides
Send 1,500,000 COP from a wallet to a beneficiary and confirm the final outcome.
Read the guide →Your request timed out. Find out whether the payout exists and what state it is in, without sending the money twice.
Read the guide →Hold a customer balance, reserve part of it for an operation, then consume or release the reservation.
Read the guide →Collect a PSE bank-transfer payment in Colombia and confirm it from webhook evidence and current status.
Read the guide →Verify, deduplicate and act on payment events without turning a webhook into a second financial action.
Read the guide →Route pay-ins across more than one provider while your product keeps one payment lifecycle and one API.
Read the guide →Connect a local collection, a conversion and a destination payout as related legs with their own state.
Read the guide →API reference
A selection from the OpenAPI 3.1 contract, which is the source of truth for every route, field and response. The complete reference is shared with credentials during evaluation.
| Method | Route | Purpose |
|---|---|---|
POST | /v4/payment-intents/ | Create a payment intent |
POST | /v4/payment-intents/{id}/execute | Execute an intent with a payment method or provider |
GET | /v4/payments/{paymentId} | Retrieve a payment |
GET | /v4/payments/{paymentId}/status | Current lifecycle status |
GET | /v4/payments/{paymentId}/timeline | Ordered lifecycle events |
GET | /v4/payments/{paymentId}/attempts | Provider attempts for the payment |
POST | /v4/payouts | Initiate a payout (idempotency key required) |
GET | /v4/payouts/{payoutId} | Payout status, provider reference and error detail |
GET | /v4/wallets/accounts | List wallet accounts |
POST | /v4/wallets/accounts | Create a wallet account |
GET | /v4/ledger/entries | List ledger entries |
GET | /v4/financial/explain | Explain how a financial entity reached its state |
What should be collected. Executed against a payment method or provider.
One collection, with its lifecycle status, timeline and provider attempts.
One request to one provider. A payment can have several.
Value sent to a recipient, funded from a wallet or another source, with its own reservation.
A balance per owner and currency, derived from ledger entries.
One side of a balanced journal that records a financial effect.
pendingprocessingrequires_actionawaiting_collectioncompletedfailedcancelledrefundedpendingprocessingcompletedfailedcancelledError model
400 for invalid requests, 401/403 for missing credentials or
permissions. Fix the request; retrying it unchanged will not help.
Rule violations return a code such as INSUFFICIENT_BALANCE or WALLET_FROZEN.
409 signals a conflict, for example an idempotency key reused with a different request.
A provider declining or failing is not an HTTP error on your request. It shows up on the operation: status
failed with errorCode and errorMessage.
When a provider has not confirmed an outcome, the operation stays in a non-terminal status. Retrying is only safe with the same idempotency key; the response tells you the current state, not a new attempt.
Error bodies carry a code and a message, as { "success": false, "error": { "code", "message" } }
on most routes. Responses do not include a retryability flag; use the rules above.
Webhooks
payment.succeeded, payment.failed, checkout.session.completed,
checkout.session.expired, checkout.session.cancelled,
checkout.session.failed. Register a callback with a URL, events and a secret. Payout status is
read by polling.
With a callback secret configured, every delivery carries X-Orangepill-Signature: sha256=<hex>,
an HMAC-SHA256 of timestamp.body, plus X-Orangepill-Timestamp. Always configure a
secret and reject stale timestamps.
Delivery is at least once. Deduplicate on the event id (also sent as
X-Orangepill-Event-Id). Failed deliveries are retried after 10 seconds, 1 minute and 5 minutes
on 5xx, 408 or 429, with a 10-second timeout.
Ordering is not guaranteed. Use the event timestamp and, when in doubt, read the current status
from the API instead of inferring it from the last event received.
// X-Orangepill-Signature: sha256=<hex>
// signed payload: `${timestamp}.${rawBody}`, using your callback secret
const expected = createHmac('sha256', secret)
.update(`${req.headers['x-orangepill-timestamp']}.${rawBody}`)
.digest('hex');
const valid = timingSafeEqual(
Buffer.from(`sha256=${expected}`),
Buffer.from(req.headers['x-orangepill-signature'])
);
Envelope: { id, event, version, timestamp, data }. The payload is identical across retries.
Sandbox
The sandbox is a separate deployment with its own data. It includes a test card provider whose outcomes are deterministic by card number: success, insufficient funds, expired card, wrong CVC, timeout, rate limiting and network errors. Like real providers, it is asynchronous and reports outcomes by webhook, so your integration handles pending states from the start. Sandbox access and test card numbers are provided during evaluation.
Architecture & Operations
Why transport failure and economic failure are different, and what evidence you need before acting again.
Read →Failover is a finality problem, not only a routing problem. Where it is safe, where it is not, and what runs today.
Read →Your system says pending, the provider says completed. Why the last webhook is not enough to decide.
Read →Integrations: providers, rails and channels by family and status are on the integrations page. Coming next: a curated public API reference, a published TypeScript SDK, a changelog and a status page.
Tell us the flow you want to build first. We will set up credentials and walk through it with you.