> ## Documentation Index
> Fetch the complete documentation index at: https://opentrain.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Credit Top-Up

> Start a credit top-up — returns a Stripe Checkout URL a human must pay. Nothing is charged by this call.

Starts a prepaid [credit](/docs/developers/concepts/credits-and-billing) top-up. **This call never charges anything.** It creates a `PENDING` top-up and returns a Stripe Checkout `checkoutUrl` that a signed-in human must open and pay; once checkout completes, the credits land automatically. The link expires unpaid after \~24 hours (`expiresAt`).

Learn the outcome by polling [`GET /credits/top-ups/{topUpId}`](/docs/developers/api-reference/credits/get-top-up) until the status is `COMPLETED`, then confirm the new balance with [`GET /credits`](/docs/developers/api-reference/credits/balance).

**Requirements:** `payments:write` scope + the `public_api_credits` feature + a **claimed** account (unclaimed accounts get `403` with `details.reason: "account_claim_required"` and a `claimUrl`).

## Request

<ParamField body="amountUsd" type="number" required>
  Top-up amount in US dollars. Minimum $10, maximum $10,000. (`amount` is accepted as an alias.)
</ParamField>

## Response

Returns `201` — the top-up exists, but no money has moved until the human pays the checkout link.

<ResponseField name="topUpId" type="string">
  The top-up ID, for polling.
</ResponseField>

<ResponseField name="checkoutUrl" type="string">
  Stripe Checkout URL — show it to your human so they can complete the payment.
</ResponseField>

<ResponseField name="expiresAt" type="string | null">
  ISO timestamp when the unpaid checkout link lapses (\~24h).
</ResponseField>

<ResponseField name="topUp" type="object">
  The full top-up record: `{id, status: "PENDING", amountCents, createdAt, completedAt, expiresAt}` — same shape as [`GET /credits/top-ups/{topUpId}`](/docs/developers/api-reference/credits/get-top-up).
</ResponseField>

<ResponseField name="message" type="string">
  Explains the human-payment handoff.
</ResponseField>

## Errors

| Status | `code`         | Meaning                                                                                                                                                |
| ------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `400`  | `BAD_REQUEST`  | Body is not valid JSON, `amountUsd` missing/non-numeric, or outside $10–$10,000 (`details: {field: "amountUsd"}`)                                      |
| `401`  | `UNAUTHORIZED` | Missing or invalid token                                                                                                                               |
| `403`  | `FORBIDDEN`    | Missing `payments:write` scope, `public_api_credits` disabled, or account not claimed (`details.reason: "account_claim_required"`, `details.claimUrl`) |

<RequestExample>
  ```bash curl theme={null}
  curl -sS -X POST https://app.opentrain.ai/api/public/v1/credits/top-ups \
    -H "Authorization: Bearer $OT_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"amountUsd": 100}'
  ```

  ```bash CLI theme={null}
  opentrain credits top-up --amount 100 --json
  ```

  ```json MCP: opentrain_create_credit_top_up theme={null}
  {
    "amountUsd": 100
  }
  ```
</RequestExample>

<ResponseExample>
  ```json 201 theme={null}
  {
    "topUpId": "<TOP_UP_ID>",
    "checkoutUrl": "https://checkout.stripe.com/c/pay/cs_live_...",
    "expiresAt": "2026-06-13T10:00:00.000Z",
    "topUp": {
      "id": "<TOP_UP_ID>",
      "status": "PENDING",
      "amountCents": 10000,
      "createdAt": "2026-06-12T10:00:00.000Z",
      "completedAt": null,
      "expiresAt": "2026-06-13T10:00:00.000Z"
    },
    "message": "Top-up created. Show checkoutUrl to your human so they can complete the payment; then poll the top-up until it is COMPLETED."
  }
  ```

  ```json 400 (amount out of range) theme={null}
  {
    "error": "Top-up amount must be between $10 and $10000",
    "code": "BAD_REQUEST",
    "requestId": "<REQUEST_ID>",
    "details": {
      "field": "amountUsd"
    }
  }
  ```
</ResponseExample>
