API v1 preview

# Pay an order

`POST /api/v1/orders/{order_id}/payments`

## Overview

Pay an existing order’s accepted total through hosted Checkout or an approved saved card. Requires explicit API spending authorization and payments:write. Required Idempotency-Key protects retries for 24 hours; only one collection can be active per order. Declines and customer verification return payment state, not a new order.

Requires `payments:write` and administrator spending authorization.

[Order and payment guide](https://ottomfg.com/docs/orders.md)

[Errors and retries](https://ottomfg.com/docs/errors.md)

Request

```
#!/usr/bin/env bash
# Bash + curl + jq. Requires payments:write and explicit spending authorization.
set -euo pipefail
: "${API_BASE_URL:?Set API_BASE_URL}"
: "${OTTOMFG_API_KEY:?Set OTTOMFG_API_KEY}"
: "${ORDER_ID:?Set ORDER_ID}"
: "${PAYMENT_REQUEST_KEY:?Set PAYMENT_REQUEST_KEY once; save and reuse on retry}"
body=$(jq -n  '{type:"checkout"}')
result=$(curl --fail-with-body --silent --show-error --max-time 60 -X POST "${API_BASE_URL%/}/api/v1/orders/$ORDER_ID/payments" \
  -H "Authorization: Bearer $OTTOMFG_API_KEY" \
  -H "Idempotency-Key: $PAYMENT_REQUEST_KEY" \
  -H "Content-Type: application/json" --data "$body") || { printf '%s\n' "$result"; exit 1; }
printf '%s\n' "$result" | jq .payment
# Deliver next_action.url securely to the customer UI; poll GET order for current state.
```

Set API_BASE_URL and OTTOMFG_API_KEY. Save request keys before creating an order or starting payment; reuse the same key and body on retry. See the guide for permissions and recovery.

**More request examples**

Request body

```
{
  "type": "checkout"
}
```

[Order setup and recovery](https://ottomfg.com/docs/orders.md)

## Parameters

`order_id` path · required

Immutable order ID and customer reference: OM- followed by eight uppercase Crockford Base32 characters. Pass it unchanged; there is no separate order-number field.

string

`Idempotency-Key` header · required

Account, method and path scoped retry key, retained for 24 hours from first use. Same body replays the original successful status and body. A changed body returns 409 idempotency_key_reused; an in-flight request returns 409 idempotency_request_in_progress with Retry-After. Retry transient failures with the same key and body.

string

## Request body

application/json

`type` string · required

Allowed values: checkout, saved_method

`payment_method_id` string · conditional

Min length: 1 · Max length: 243

Exactly one of these conditions applies:

**checkout**

Absent fields: payment_method_id.

`type` string · required

Always: `"checkout"`

**saved_method**

Required fields: payment_method_id.

`type` string · required

Always: `"saved_method"`

## Responses

Payment state and optional interaction required; success of this HTTP call does not imply paid.

200 response

```
{
  "order_id": "OM-XMF1NT2E",
  "payment": {
    "status": "requires_action",
    "amount": 7000,
    "currency": "usd",
    "method": null,
    "paid_at": null,
    "receipt_url": null
  },
  "next_action": {
    "type": "redirect",
    "url": "https://payments.example.com/checkout/example",
    "expires_at": "2026-09-18T19:00:00.000000Z"
  }
}
```

`order_id` string · required

Immutable order ID and customer reference: OM- followed by eight uppercase Crockford Base32 characters. Pass it unchanged; there is no separate order-number field.

Min length: 11 · Max length: 11

`payment` object · required

Order-bound payment state, independent of preparation/fulfillment. amount/currency come from accepted quote. A saved method or return redirect is not proof of payment. Receipt links may be absent; never expose client secrets here. Refund states describe full-order refunds in v1. Cancellation before production refunds the full accepted total, including shipping. Refund completion is asynchronous; refund_failed requires support.

**payment fields**

`status` string · required

Order payment state. Refund states describe full-order refunds.

Allowed values: unpaid, requires_action, processing, paid, failed, cancelled, refund_pending, refunded, refund_failed

`amount` integer · required

Minimum: 0

`currency` string · required

`method` object | null · required

**method fields**

At least one of these conditions applies:

**object**

`id` string · required

`brand` string · required

`last4` string · required

**null**

null

`paid_at` string (date-time) | null · required

`receipt_url` string (uri) | null · required

Receipt link after successful payment, when available; otherwise null. Retrieve the order for current payment details.

`error` object · optional

Error code, message and optional field details. HTTP errors return this object directly; asynchronous processing errors appear on the resource. Branch on code, not message.

[Error object](https://ottomfg.com/docs/error-object.md)

`next_action` object | null · required

**next_action fields**

At least one of these conditions applies:

**redirect**

`type` string · required

Always: `"redirect"`

`url` string (uri) · required

`expires_at` string (date-time) · required

**null**

null

**Response headers**

`X-Request-ID`

Request identifier for support. Send X-Request-ID with 8–64 ASCII letters, digits or hyphens to reuse that value; otherwise the server generates a UUID4 hex value. Include the returned X-Request-ID in support requests. Do not put secrets or account IDs in this value.

`Idempotent-Replayed`

Present when the original successful response is replayed.

## Errors

| HTTP | Code | What to do |
| --- | --- | --- |
| 400 Bad Request | `invalid_argument` | A field, query parameter or header is invalid. Fix the field named in details, then send the request again. |
| 400 Bad Request | `payment_method_not_found` | No saved card with this ID is approved for the business. Choose a card from List payment methods. |
| 401 Unauthorized | `unauthenticated` | The API key is missing, invalid or revoked. Send a valid business API key in Authorization: Bearer. |
| 403 Forbidden | `permission_denied` | The key lacks the endpoint's scope, or payments lack spending consent. Check the scope listed on the endpoint. |
| 404 Not Found | `order_not_found` | No order with this ID exists in the account. Check the ID. |
| 409 Conflict | `idempotency_key_reused` | This Idempotency-Key was already used with a different body. Retry the original body, or use a new key for a new operation. |
| 409 Conflict | `idempotency_request_in_progress` | A request with this Idempotency-Key is still running. Wait Retry-After seconds, then retry the same key and body. |
| 409 Conflict | `model_repair_estimate_pending` | The repair fee is still being estimated. Wait until the model status is ready, then retry. |
| 409 Conflict | `payment_in_progress` | A payment with a different method is still in progress. Wait for its result before you try another method. |
| 409 Conflict | `payment_method_unavailable` | The saved card cannot be charged, for example because its billing address is invalid. Update it, then retry. |
| 409 Conflict | `payment_reconciliation_required` | The payment outcome is unknown and needs review. Contact support with the order ID; do not pay again. |
| 409 Conflict | `payment_unavailable` | The order cannot take a payment now. Retrieve it and check its status and expires_at. |
| 409 Conflict | `quote_unavailable` | A model, its price or its repair changed after quoting. Create and review a new quote. |
| 409 Conflict | `repair_payment_pending` | Another order has an unpaid repair fee for this file revision. Pay or cancel that order, then quote again. |
| 413 Content Too Large | `request_too_large` | The JSON body is over this endpoint's size limit. Send a smaller body. |
| 415 Unsupported Media Type | `unsupported_media_type` | The body is not JSON. Send Content-Type: application/json. |
| 429 Too Many Requests | `resource_exhausted` | A rate or account limit was reached. Wait at least Retry-After seconds, then retry. |
| 500 Internal Server Error | `internal` | The server failed unexpectedly. Retry with backoff; contact support with X-Request-ID if it persists. |
| 503 Service Unavailable | `unavailable` | A required service is briefly unavailable. Retry with backoff, reusing the same Idempotency-Key. |

[Idempotent requests](https://ottomfg.com/docs/idempotency.md)

API v1 preview

Source: https://ottomfg.com/docs/pay-order
