Skip to main content
POST
/
api
/
public
/
v1
/
credits
/
top-ups
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}'
{
  "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."
}
Starts a prepaid credit 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} until the status is COMPLETED, then confirm the new balance with GET /credits. 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

amountUsd
number
required
Top-up amount in US dollars. Minimum 10,maximum10, maximum 10,000. (amount is accepted as an alias.)

Response

Returns 201 — the top-up exists, but no money has moved until the human pays the checkout link.
topUpId
string
The top-up ID, for polling.
checkoutUrl
string
Stripe Checkout URL — show it to your human so they can complete the payment.
expiresAt
string | null
ISO timestamp when the unpaid checkout link lapses (~24h).
topUp
object
The full top-up record: {id, status: "PENDING", amountCents, createdAt, completedAt, expiresAt} — same shape as GET /credits/top-ups/{topUpId}.
message
string
Explains the human-payment handoff.

Errors

StatuscodeMeaning
400BAD_REQUESTBody is not valid JSON, amountUsd missing/non-numeric, or outside 1010–10,000 (details: {field: "amountUsd"})
401UNAUTHORIZEDMissing or invalid token
403FORBIDDENMissing payments:write scope, public_api_credits disabled, or account not claimed (details.reason: "account_claim_required", details.claimUrl)
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}'
{
  "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."
}