Skip to main content
Version: Next

API reference

Authentication

Bearer token in the Authorization header: Authorization: Bearer $ION_SECRET_KEY. Test-mode keys (sk_test_...) and live-mode keys (sk_live_...) are separate credentials — a test key can never move real money, even by accident.

Create a charge

POST /v1/ion/charges

Request

{
"amount": 4200,
"currency": "usd",
"customer": "cus_h82ndk3",
"payment_method": "pm_1a2b3c",
"idempotency_key": "order_9f3e-attempt-1"
}

Response200 OK

{
"id": "ch_3P8kq2Aa",
"amount": 4200,
"currency": "usd",
"status": "succeeded",
"customer": "cus_h82ndk3",
"created": "2026-07-20T09:14:22Z"
}

status is one of succeeded, pending (some local payment methods settle asynchronously), or failed.

Create a customer

POST /v1/ion/customers

{
"email": "jordan@acme.com",
"payment_method": "pm_1a2b3c"
}

Response includes the customer id (cus_...) to reuse on future charges and subscriptions.

Create a subscription

POST /v1/ion/subscriptions

{
"customer": "cus_h82ndk3",
"plan": "plan_pro_monthly",
"idempotency_key": "sub_acme_pro-2026-07"
}

Response

{
"id": "sub_7k2p9x",
"status": "active",
"current_period_end": "2026-08-20T09:14:22Z"
}

Issue a refund

POST /v1/ion/refunds

{
"charge": "ch_3P8kq2Aa",
"amount": 2000
}

Omit amount to refund the charge in full. Partial refunds can be issued multiple times against the same charge as long as their total doesn't exceed the original amount.

Errors

HTTP statuscodeMeaning
400invalid_currencyCurrency not supported, or amount invalid for that currency's subunit
402card_declinedThe payment method was declined by the issuer
404no_such_chargeThe referenced charge/customer/payment_method ID doesn't exist
409idempotency_key_reusedSame key used with a different request body than the original
429rate_limitedBack off using the Retry-After header

card_declined responses include a decline_code (insufficient_funds, expired_card, generic_decline, …) — surface this to the end user rather than a generic "payment failed" message where possible; it materially reduces retry-and-fail loops at checkout.

Rate limits

100 requests/second per API key across all endpoints. Charge creation specifically is additionally capped at 25/second per key to limit the blast radius of a runaway retry loop.

Support

For questions about Ion, contact the product team or visit the community forum.