Penumbra's API is REST/JSON, authenticated with a single bearer token. Routing decisions, transactions, disputes, ledger, webhooks, merchant boarding, settlement, payouts. All under api.penumbrahq.com/v1/. SDKs in six languages.
Get your first routing decision back in under five minutes. You'll need a Penumbra account and a sandbox API key from the dashboard.
sk_test_… key. Revoke and rotate from the same screen./v1/routing/decide with a transaction shape. You'll get back the winning processor, the runner-up, and the reasoning.processor to call the right downstream PSP. Or let Penumbra submit it for you with POST /v1/routing/execute.transaction.authorized / captured / refunded / etc. events in the Pen.v1 schema.sk_test_ for sk_live_. No code changes. Webhook URLs and signing secrets are per-environment so the cutover is clean.Below: the same first request, in six languages.
curl https://api.penumbrahq.com/v1/routing/decide \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ -d '{ "amount_cents": 124700, "currency": "USD", "card_bin": "424242", "mcc": "5812", "merchant_id": "mer_a4b9c2e1" }'
import Penumbra from '@penumbra/node'; const penumbra = new Penumbra(process.env.PENUMBRA_KEY); const decision = await penumbra.routing.decide({ amount_cents: 124700, currency: 'USD', card_bin: '424242', mcc: '5812', merchant_id: 'mer_a4b9c2e1', }); console.log(decision.chosen.processor); // 'adyen'
import penumbra import os penumbra.api_key = os.environ["PENUMBRA_KEY"] decision = penumbra.Routing.decide( amount_cents=124700, currency="USD", card_bin="424242", mcc="5812", merchant_id="mer_a4b9c2e1", ) print(decision.chosen.processor) # 'adyen'
import "github.com/penumbrahq/penumbra-go" client := penumbra.NewClient(os.Getenv("PENUMBRA_KEY")) decision, err := client.Routing.Decide(ctx, &penumbra.DecideParams{ AmountCents: 124700, Currency: "USD", CardBIN: "424242", MCC: "5812", MerchantID: "mer_a4b9c2e1", })
require "penumbra" Penumbra.api_key = ENV["PENUMBRA_KEY"] decision = Penumbra::Routing.decide( amount_cents: 124700, currency: "USD", card_bin: "424242", mcc: "5812", merchant_id: "mer_a4b9c2e1", )
require_once 'vendor/autoload.php'; \Penumbra\Penumbra::setApiKey(getenv('PENUMBRA_KEY')); $decision = \Penumbra\Routing::decide([ 'amount_cents' => 124700, 'currency' => 'USD', 'card_bin' => '424242', 'mcc' => '5812', 'merchant_id' => 'mer_a4b9c2e1', ]);
Officially maintained client libraries. All track the same API surface; new endpoints land in every SDK within one release.
Every request carries a bearer token in the Authorization header. Tokens are environment-scoped, scope-restricted, and rotatable.
| Prefix | Environment | Use |
|---|---|---|
sk_test_… | Sandbox | Full API access against simulated processors. No real money moves. |
sk_live_… | Production | Real charges to real processors. Treat as the password to your money. |
pk_… | Publishable | Client-side tokenization only. Cannot create charges or move money. |
rk_… | Restricted | Custom-scoped (e.g. read-only, reports-only). Created in dashboard. |
Rotate from Settings → API keys. Revocations propagate to all edge nodes in under 60 seconds. We recommend rotating live keys quarterly and immediately after any suspected exposure.
Standard HTTP status codes. 2xx success, 4xx client error, 5xx server error. Every error body has a type, a human-readable message, and a request_id you can quote to support.
{
"error": {
"type": "invalid_request_error",
"code": "missing_card_bin",
"message": "card_bin is required for card-not-present routing decisions",
"request_id": "req_8f3d2a17b4c19",
"doc_url": "https://penumbrahq.com/docs/errors#missing_card_bin"
}
}
Safe to retry: any 5xx, plus 429 (rate limit) and 409 (idempotency conflict mid-write). Use exponential backoff starting at 250ms, max 30s.
Not safe to retry without idempotency keys: any 2xx followed by a network error. The request may have succeeded server-side. Always include an Idempotency-Key header on write endpoints.
Per-key, sliding-window. Limits scale with tier; defaults below.
| Endpoint class | Sandbox | Live (Pro) | Live (Scale+) |
|---|---|---|---|
| Routing decisions | 25 req/s | 100 req/s | 1,000 req/s |
| Read endpoints | 50 req/s | 200 req/s | 2,000 req/s |
| Write endpoints (non-routing) | 10 req/s | 50 req/s | 500 req/s |
| Webhook subscribe/configure | 5 req/s | 10 req/s | 20 req/s |
Limits surface in response headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. Exceed the limit and you get a 429 with a Retry-After header in seconds.
Every write endpoint accepts an Idempotency-Key header. Send the same key with the same payload and we'll return the cached response — never double-charge, never double-publish. Keys are remembered for 24 hours.
curl https://api.penumbrahq.com/v1/routing/execute \ -H "Authorization: Bearer sk_live_..." \ -H "Idempotency-Key: order_a4b9c2e1_attempt_1" \ -d '{...}'
UUIDs work; opaque order IDs work better because they're deterministic if you retry from your application code.
Cursor-based, forward and backward. List endpoints return up to 100 records per page by default.
GET /v1/transactions?limit=50&starting_after=txn_8f3d2a17 // Response { "object": "list", "data": [...], "has_more": true, "next_cursor": "txn_d12e54b8" }
The routing engine scores every connected processor on approval probability, latency, and cost. Submit a transaction shape and get a ranked recommendation in milliseconds. Or have us submit it for you.
{
"decision_id": "dec_4Ag8nP2",
"chosen": {
"processor": "adyen",
"approval_probability": 0.956,
"expected_cost_bps": 241,
"latency_ms_p50": 12
},
"runner_up": { "processor": "stripe", "approval_probability": 0.951 },
"reason": "adyen_lower_cost_at_equivalent_approval",
"policy_id": "pol_default"
}
List, retrieve, and inspect every charge processed through Penumbra. Each transaction carries the original routing decision and the response from the processor.
Filter by processor, status, amount range, time window, MCC, card brand. Each row exposes the routing decision_id so you can replay the decision against current policies.
The disputes engine ingests carrier notifications, assembles evidence, drafts the rebuttal, and submits when you approve. Stripe, Adyen, Worldpay, NMI all supported as submission targets.
Double-entry immutable ledger with full chain-of-custody. Every transaction, payout, fee, refund, reserve adjustment writes journal entries you can audit. Built on the same accounting primitives as the operator dashboard.
Schedule, inspect, or override payouts to merchants and to platform-fee accounts. ACH, Same-Day ACH, FedNow when sponsor-bank enrollment closes, and USDC settlement via Circle across five chains.
Submit boarding applications, retrieve KYB/risk decisions, manage MIDs across the processors a merchant boarded with. Penumbra holds a thin merchant-identity record; PSP underwriting decisions are passed through.
Subscribe to any of the 41 canonical Pen.v1 events. HMAC-SHA256 signed, retried with exponential backoff up to 24 hours, 99.9% delivery SLO.
Full event catalogue with payloads at Webhook events.
Generate a sandbox key, hit the routing engine with your own payloads, watch the decisions come back. No sales call required.