---
id: api-stripe-compatibility
title: Stripe API compatibility
summary: The Stripe-shaped routes, for servers already written against Stripe.
faces: ["public", "agent"]
section: back-end
group: API reference
slug: stripe-compatibility
order: 50
next: ["api-openapi"]
---
# Stripe API compatibility

These routes accept what a Stripe server already sends, so an existing integration can point at ripper with
fewer changes. They take your secret key as a bearer token, which is what a Stripe client sends. The native
routes above are the ones to write against for anything new.

<!-- generated from docs/api/openapi.yaml — edit the spec, not this -->

### POST /v1/compat/stripe/payment_intents

Create a PaymentIntent (Stripe-compatible)

**Authentication.** stripeCompatBearer

Creates a ripper checkout session and answers it as a Stripe PaymentIntent; `id` is the session id.
Accepts both forms an unchanged Stripe server sends: form encoding (what Stripe's libraries send,
with `metadata[key]=value`) and JSON. A parameter ripper does not support is refused by name with
`parameter_unknown` rather than ignored. `description` is kept as `metadata.description`.

Uses the same secret key as the rest of the API, in Stripe's position: `Authorization: Bearer`.

| parameter | in | required | what it is |
|---|---|---|---|
| `Idempotency-Key` | header | no | Optional. `Rip-Idempotency-Key` is accepted too. Without one the create still succeeds, but a retry creates a second session. |

**Fields in the body you send**

| field | type | always sent | what it is |
|---|---|---|---|
| `amount` | integer | yes | Minor units. |
| `currency` | string | yes | Three letters, any case. |
| `metadata` | object | no | Sent as `metadata[key]=value` in form encoding. If your integration names an order-reference metadata key, that value becomes the order reference. |
| `receipt_email` | string | no | The shopper's email. Every ripper payment needs one. |
| `description` | string | no | Kept as `metadata.description`. |
| `statement_descriptor_suffix` | string | no | Added to the name on the shopper’s card statement. |
| `capture_method` | string | no | Only `automatic` is supported. |

**200 —** The PaymentIntent.

**Fields in the response**

| field | type | always sent | what it is |
|---|---|---|---|
| `id` | string | yes | The ripper session id. |
| `object` | string | yes | Always `payment_intent`, as Stripe sends it. |
| `amount` | integer | yes | What will be charged, in the currency’s smallest unit. |
| `currency` | string | yes | Lower case, as Stripe spells it. |
| `status` | string | yes | The intent’s status, in Stripe’s vocabulary rather than ripper’s. |
| `client_secret` | string or null | yes | The ripper client secret on create; null on a read. |
| `created` | integer | yes | Unix seconds. |
| `livemode` | boolean | yes | `false` in test mode, as Stripe sends it. |
| `metadata` | object | yes | Your own values, keys exactly as you sent them. |
| `statement_descriptor_suffix` | string or null | yes | Added to the name on the shopper’s card statement. |
| `receipt_email` | string or null | yes | Where a receipt would be sent. |
| `capture_method` | string | yes | Whether the payment is captured straight away or left authorised. |
| `last_payment_error` | object or null | yes | The most recent failure on this intent, or `null`. |
| `next_action` | null | yes | What the shopper must do next, or `null` when nothing is needed. |
| `ripper` | object | yes | The parts of this payment Stripe has no field for. Everything else on this object is Stripe’s shape. |
| `cancellation_reason` | string or null | yes | Why the intent was cancelled, or `null`. |

**`last_payment_error` fields**

| field | type | always sent | what it is |
|---|---|---|---|
| `code` | string | no | Stripe’s error code. |
| `decline_code` | string or null | no | Stripe’s decline code, when the issuer gave one. |
| `message` | string | no | Help for a person reading logs. It may change; never branch on it. |

**`ripper` fields**

| field | type | always sent | what it is |
|---|---|---|---|
| `checkout_session_id` | string | yes | The ripper session behind this intent. |
| `amount_verified` | boolean | yes | Whether ripper could check the amount against your price list before charging it. When it could not, an approved payment is held for you to review rather than captured. |
| `hold` | object or null | yes | The hold on this payment, or `null` when there is none. Stripe has no equivalent. |

**`ripper.hold` fields**

| field | type | always sent | what it is |
|---|---|---|---|
| `status` | string | yes | Where the hold is. `pending_review` and `escalated` are both still waiting on a decision — escalated means a person is looking at it rather than a rule. `captured` means the money was taken. `voided` means the hold was released and nothing was taken, and it is final: it is not another kind of waiting. DO NOT RELEASE GOODS until this says `captured`. That holds for all four values, including escalated, which can still end either way. |
| `reason` | string | yes | `no_price_list`: there was nothing to check the amount against. `mismatch`: the amount did not match. |
| `detail` | string or null | yes | More about why, when there is more to say. |
| `deadline` | string | yes | When the hold must be decided by. |
| `decided_by` | string or null | yes | Who decided: you, or ripper on your rules. |
| `decided_at` | string or null | yes | When the decision was made, or `null` while it is still waiting. |
| `operation_refusal` | object or null | yes | The last capture or void the acquirer refused, if any. |

**`ripper.hold.operation_refusal` fields**

| field | type | always sent | what it is |
|---|---|---|---|
| `kind` | string | yes | Which operation was refused. |
| `sequence` | integer | yes | Which attempt this refusal belongs to. |
| `reason` | string | yes | Why it was refused. |
| `decline_code` | string or null | yes | The decline code, when the refusal came from the acquirer. |
| `at` | string | yes | When the refusal happened. |

**When it refuses**

- `400` — A Stripe-shaped error. `error.type` follows Stripe (`invalid_request_error`, `authentication_error`, `permission_error`, `api_error`); `error.code` is Stripe's code where one applies (`parameter_unknown`, `resource_missing`, `idempotency_key_in_use`) and otherwise the API error code, which is always in `error.ripper_code`.
- `401` — A Stripe-shaped error. `error.type` follows Stripe (`invalid_request_error`, `authentication_error`, `permission_error`, `api_error`); `error.code` is Stripe's code where one applies (`parameter_unknown`, `resource_missing`, `idempotency_key_in_use`) and otherwise the API error code, which is always in `error.ripper_code`.
- `403` — A Stripe-shaped error. `error.type` follows Stripe (`invalid_request_error`, `authentication_error`, `permission_error`, `api_error`); `error.code` is Stripe's code where one applies (`parameter_unknown`, `resource_missing`, `idempotency_key_in_use`) and otherwise the API error code, which is always in `error.ripper_code`.
- `409` — A Stripe-shaped error. `error.type` follows Stripe (`invalid_request_error`, `authentication_error`, `permission_error`, `api_error`); `error.code` is Stripe's code where one applies (`parameter_unknown`, `resource_missing`, `idempotency_key_in_use`) and otherwise the API error code, which is always in `error.ripper_code`.
- `429` — Too many requests from your address in a minute. This answer comes from ripper's gateway, so its body is not the error shape above. Wait for `Retry-After` seconds.
- `500` — A Stripe-shaped error. `error.type` follows Stripe (`invalid_request_error`, `authentication_error`, `permission_error`, `api_error`); `error.code` is Stripe's code where one applies (`parameter_unknown`, `resource_missing`, `idempotency_key_in_use`) and otherwise the API error code, which is always in `error.ripper_code`.
- `503` — A Stripe-shaped error. `error.type` follows Stripe (`invalid_request_error`, `authentication_error`, `permission_error`, `api_error`); `error.code` is Stripe's code where one applies (`parameter_unknown`, `resource_missing`, `idempotency_key_in_use`) and otherwise the API error code, which is always in `error.ripper_code`.

Operation id `createStripePaymentIntent`, for code generators.

### GET /v1/compat/stripe/payment_intents/{session_id}

Retrieve a PaymentIntent (Stripe-compatible)

**Authentication.** stripeCompatBearer

Reads a session as a Stripe PaymentIntent. The client secret is not returned on a read.

| parameter | in | required | what it is |
|---|---|---|---|
| `session_id` | path | yes | The session's id. |

**200 —** The PaymentIntent.

**Fields in the response**

| field | type | always sent | what it is |
|---|---|---|---|
| `id` | string | yes | The ripper session id. |
| `object` | string | yes | Always `payment_intent`, as Stripe sends it. |
| `amount` | integer | yes | What will be charged, in the currency’s smallest unit. |
| `currency` | string | yes | Lower case, as Stripe spells it. |
| `status` | string | yes | The intent’s status, in Stripe’s vocabulary rather than ripper’s. |
| `client_secret` | string or null | yes | The ripper client secret on create; null on a read. |
| `created` | integer | yes | Unix seconds. |
| `livemode` | boolean | yes | `false` in test mode, as Stripe sends it. |
| `metadata` | object | yes | Your own values, keys exactly as you sent them. |
| `statement_descriptor_suffix` | string or null | yes | Added to the name on the shopper’s card statement. |
| `receipt_email` | string or null | yes | Where a receipt would be sent. |
| `capture_method` | string | yes | Whether the payment is captured straight away or left authorised. |
| `last_payment_error` | object or null | yes | The most recent failure on this intent, or `null`. |
| `next_action` | null | yes | What the shopper must do next, or `null` when nothing is needed. |
| `ripper` | object | yes | The parts of this payment Stripe has no field for. Everything else on this object is Stripe’s shape. |
| `cancellation_reason` | string or null | yes | Why the intent was cancelled, or `null`. |

**`last_payment_error` fields**

| field | type | always sent | what it is |
|---|---|---|---|
| `code` | string | no | Stripe’s error code. |
| `decline_code` | string or null | no | Stripe’s decline code, when the issuer gave one. |
| `message` | string | no | Help for a person reading logs. It may change; never branch on it. |

**`ripper` fields**

| field | type | always sent | what it is |
|---|---|---|---|
| `checkout_session_id` | string | yes | The ripper session behind this intent. |
| `amount_verified` | boolean | yes | Whether ripper could check the amount against your price list before charging it. When it could not, an approved payment is held for you to review rather than captured. |
| `hold` | object or null | yes | The hold on this payment, or `null` when there is none. Stripe has no equivalent. |

**`ripper.hold` fields**

| field | type | always sent | what it is |
|---|---|---|---|
| `status` | string | yes | Where the hold is. `pending_review` and `escalated` are both still waiting on a decision — escalated means a person is looking at it rather than a rule. `captured` means the money was taken. `voided` means the hold was released and nothing was taken, and it is final: it is not another kind of waiting. DO NOT RELEASE GOODS until this says `captured`. That holds for all four values, including escalated, which can still end either way. |
| `reason` | string | yes | `no_price_list`: there was nothing to check the amount against. `mismatch`: the amount did not match. |
| `detail` | string or null | yes | More about why, when there is more to say. |
| `deadline` | string | yes | When the hold must be decided by. |
| `decided_by` | string or null | yes | Who decided: you, or ripper on your rules. |
| `decided_at` | string or null | yes | When the decision was made, or `null` while it is still waiting. |
| `operation_refusal` | object or null | yes | The last capture or void the acquirer refused, if any. |

**`ripper.hold.operation_refusal` fields**

| field | type | always sent | what it is |
|---|---|---|---|
| `kind` | string | yes | Which operation was refused. |
| `sequence` | integer | yes | Which attempt this refusal belongs to. |
| `reason` | string | yes | Why it was refused. |
| `decline_code` | string or null | yes | The decline code, when the refusal came from the acquirer. |
| `at` | string | yes | When the refusal happened. |

**When it refuses**

- `401` — A Stripe-shaped error. `error.type` follows Stripe (`invalid_request_error`, `authentication_error`, `permission_error`, `api_error`); `error.code` is Stripe's code where one applies (`parameter_unknown`, `resource_missing`, `idempotency_key_in_use`) and otherwise the API error code, which is always in `error.ripper_code`.
- `403` — A Stripe-shaped error. `error.type` follows Stripe (`invalid_request_error`, `authentication_error`, `permission_error`, `api_error`); `error.code` is Stripe's code where one applies (`parameter_unknown`, `resource_missing`, `idempotency_key_in_use`) and otherwise the API error code, which is always in `error.ripper_code`.
- `404` — A Stripe-shaped error. `error.type` follows Stripe (`invalid_request_error`, `authentication_error`, `permission_error`, `api_error`); `error.code` is Stripe's code where one applies (`parameter_unknown`, `resource_missing`, `idempotency_key_in_use`) and otherwise the API error code, which is always in `error.ripper_code`.
- `429` — Too many requests from your address in a minute. This answer comes from ripper's gateway, so its body is not the error shape above. Wait for `Retry-After` seconds.
- `500` — A Stripe-shaped error. `error.type` follows Stripe (`invalid_request_error`, `authentication_error`, `permission_error`, `api_error`); `error.code` is Stripe's code where one applies (`parameter_unknown`, `resource_missing`, `idempotency_key_in_use`) and otherwise the API error code, which is always in `error.ripper_code`.

Operation id `getStripePaymentIntent`, for code generators.

<!-- end generated -->
