Authorize and Capture
Most Throttle checkouts authorize and capture in one step. Splitting them lets you hold funds at order time and take the money when you actually ship — and take less than you held if the final total came in lower.
The routes
| Route | Body | Constraint | Scope |
|---|---|---|---|
POST /api/v1/orders/{id}/authorize | { paymentId } | Order must be pending | payment_authorizations:write |
POST /api/v1/orders/{id}/capture | { paymentId } | Full amount only | payment_captures:write |
POST /api/v1/payments/{id}/authorize | None | Applies the order guard when linked | payment_authorizations:write |
POST /api/v1/payments/{id}/capture | { amount? } | Partial capture lives here | payment_captures:write |
POST /api/v1/orders/{id}/close | None | Only from completed | orders:write |
The order-scoped and payment-scoped routes do the same underlying work. Use the order-scoped pair when you are holding an order id and want Throttle to keep the order status in step; use the payment-scoped pair when you are working from a payment.
Partial capture
Only POST /api/v1/payments/{id}/capture
accepts an amount. Omit it and Throttle
captures the full authorized amount; pass one and it captures exactly
that, in minor units.
curl -X POST https://api.usethrottle.dev/api/v1/payments/pay_88ce.../capture \
-H "X-API-Key: $THROTTLE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "amount": 4200 }'
Over-capturing is rejected with 400 capture_amount_exceeds_authorized,
and a zero or fractional amount with 400 invalid_capture_amount.
amount vs capturedAmount
After a partial capture the payment keeps two figures, and they mean different things:
{
"data": {
"id": "pay_88ce...",
"status": "captured",
"amount": 6000,
"capturedAmount": 4200,
"capturedAt": "2026-08-09T14:22:40.000Z",
"currency": "USD"
}
} -
amount— what was authorized. It does not shrink to match the capture. -
capturedAmount— what was actually taken, and what the buyer is charged.
capturedAmount. Reading amount overstates revenue on every
partially captured order.
Full capture
The order-scoped route captures the whole authorization and keeps the
order status aligned. The order must be in pending, processing, partially_fulfilled, or completed.
curl -X POST https://api.usethrottle.dev/api/v1/orders/ord_9f2a.../capture \
-H "X-API-Key: $THROTTLE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "paymentId": "pay_88ce..." }' POST /orders/{id}/capture and /authorize check the payment first and
treat an already-captured or already-authorized payment as a no-op, so a
duplicated webhook or a retried job is harmless.
The payment-scoped routes do not. Calling
POST /payments/{id}/capture on
a payment that is already captured, or /authorize on one that is no longer pending, fails. If you are driving these
from a queue or a webhook handler, prefer the order-scoped pair or send an Idempotency-Key.
Authorizing
POST /api/v1/orders/{id}/authorize
requires the order to be pending —
authorization is the first money step, so anything further along is a 400 invalid_order_state. On this route,
re-authorizing an already-authorized payment is a no-op; on the
payment-scoped route it is not (see above).
Cancelling an order voids any open authorization automatically, so you do not need to release a hold by hand. See Order states.
Closing an order
POST /api/v1/orders/{id}/close
moves a completed order to closed, the terminal archive state. It is
the only transition out of completed, and
it moves no money.
Webhooks
A successful capture emits payment.captured carrying both figures,
so a consumer can tell a partial capture from a full one without
re-fetching:
{
"type": "payment.captured",
"data": {
"paymentId": "pay_88ce...",
"orderId": "ord_9f2a...",
"amount": 4200,
"capturedAmount": 4200,
"authorizedAmount": 6000,
"currency": "USD",
"processor": "embedded",
"processorTransactionId": "txn_..."
}
}
A processor decline emits both payment.capture_failed and payment.failed. Handle whichever suits
you, but expect both to arrive for a single failure.
Error codes
| Code | HTTP | Meaning |
|---|---|---|
invalid_capture_amount | 400 | amount is not a positive integer. |
capture_amount_exceeds_authorized | 400 | Asked for more than the authorization holds. |
invalid_order_state | 400 | The order status does not permit this transition. |
invalid_payment_state | 400 | The payment is past the point where this action applies. |
payment_order_mismatch | 400 | That payment does not belong to the order in the path. |
Full list and envelope shape on Error codes, and payment status meanings on Payment states.