Developers

Evaluate the API now.

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.

Quickstart

From credentials to a verified payout.

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.

2. Choose an environment

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.

3. Create the operation

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>"
  }
}

4. Follow the lifecycle

Poll GET /v4/payouts/{payoutId}. For payments, use /status, /timeline and /attempts, or subscribe to webhooks.

5. Treat non-terminal as unknown

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.

6. Confirm finality

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.

  1. Request accepted Idempotency key checked
  2. Reservation reservationId recorded
  3. Attempt sent Provider executes
  4. Outcome observed Status moves to terminal
  5. Ledger posted Balance reflects the outcome

Core concepts

The objects behind every financial operation, and where they appear in the API.

Economic Intent

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.

Flow / Journey

The orchestration of steps, decisions and user interactions around an operation. Flows decide what should happen next; execution moves the value.

Execution and attempts

One governed operation and the provider attempts made for it. Each attempt keeps its own state, visible through the attempts and timeline endpoints.

Wallets and ledger

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.

Finality

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.

Idempotency

Payouts require an idempotency_key, unique per tenant. Payments and refunds accept one. Reusing a key returns the existing operation instead of creating another.

In Development

Proof of Record

Evidence of how an outcome was produced. Today: settlement Proof of Record tokens, timelines, attempts and the explain endpoint.

In Development

Resolve

Governed cases for operations that cannot safely continue: Investigate → Establish → Decide → Verify.

Private Preview

Agent Runtime

Agents connect through the Orangepill MCP server with OAuth 2.0 (client credentials or PKCE) and act through scoped tools and economic intents.

API reference

Representative routes from the v4 contract.

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.

MethodRoutePurpose
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

Core objects

Payment intent

What should be collected. Executed against a payment method or provider.

Payment

One collection, with its lifecycle status, timeline and provider attempts.

Payment attempt

One request to one provider. A payment can have several.

Payout

Value sent to a recipient, funded from a wallet or another source, with its own reservation.

Wallet account

A balance per owner and currency, derived from ledger entries.

Ledger entry

One side of a balanced journal that records a financial effect.

Lifecycle statuses

  • pending
  • processing
  • requires_action
  • awaiting_collection
  • completed
  • failed
  • cancelled
  • refunded
  • pending
  • processing
  • completed
  • failed
  • cancelled

Error model

An error and an unknown outcome are different things.

Validation and authentication

400 for invalid requests, 401/403 for missing credentials or permissions. Fix the request; retrying it unchanged will not help.

Business rules and conflicts

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.

Provider errors

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.

Uncertain execution

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

Signed, retried, and safe to receive twice.

Events

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.

Signatures

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.

Duplicates and retries

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

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

Test failure, not only success.

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.

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.

Get sandbox access and the full API reference.

Tell us the flow you want to build first. We will set up credentials and walk through it with you.