---
title: "Buy Credits (machine payment)"
method: POST
path: "/v1/topup"
---

# Buy Credits (machine payment)

`POST /v1/topup`

## Request

- Base URL: `https://api.myotp.app`
- URL: `https://api.myotp.app/v1/topup`
- Auth: none declared. Required headers: `X-API-Key`

Buy credits for the account owning the API key, paid by the caller itself over the Machine Payments Protocol (MPP, https://mpp.dev). No checkout page and no card form.

The first call answers 402 Payment Required with a WWW-Authenticate header carrying two Payment challenges, one for USDC on Tempo (method "tempo") and one for card or Link via a Stripe shared payment token (method "stripe"). The client pays one of them and repeats the same request with the payment credential; any MPP client (mppx, the Stripe Link CLI) does this automatically. The repeated request answers 200 and the credits are on the balance at once.

Rules: $0.02 per credit, minimum 25 credits, maximum 50,000 per call. Card top-ups are capped at $100 per account per rolling 24 hours; over the cap the 402 offers USDC only, which is uncapped. A trial account moves to the Starter pay-as-you-go price table on its first top-up and no subscription is created. A payment credential never credits twice: a replay answers 200 with status "already_credited".

Pay from the command line with a USDC wallet (npx -y mppx@0.9.2 https://api.myotp.app/v1/topup -X POST -H "x-api-key: KEY" -H "content-type: application/json" -d '{"credits":100}') or with a Link agent wallet (npx -y @stripe/link-cli mpp pay https://api.myotp.app/v1/topup -X POST -d '{"credits":100}' -H "x-api-key: KEY" --context "Buying MyOTP.App credits to send one-time passcodes over SMS, WhatsApp and Telegram for phone verification in my app.").

## Headers

- `X-API-Key` string, required
- `Authorization` string

## Request body

- object
  - `credits` integer, required: Number of credits to buy.

## Response `200`

Payment accepted and credits applied. status is "credited" on the first settlement of a credential and "already_credited" when the same credential is replayed.

- TopupResult
  - `status` 'credited' | 'already_credited': already_credited means the credential was replayed and nothing was charged again.
  - `credits` integer: Credits bought in this call.
  - `amount_usd` string: Amount paid, in US dollars.
  - `currency` string
  - `payment` object
    - `method` 'tempo' | 'stripe'
    - `reference` string: Chain transaction hash for tempo, Stripe payment reference for stripe.
  - `balance` number: Account balance in credits after this top-up.
  - `plan_id` integer: The account's plan after this top-up. Trial accounts move to Starter pay-as-you-go on their first top-up.

## Other responses

- `400`: credits is missing, not an integer, or outside 25 to 50000.
- `401`: Missing or invalid X-API-Key.
- `402`: Payment Required. No valid payment credential was presented, so the response carries the payment challenges to satisfy. Pay one and repeat the identical request with the credential.
- `403`: The account is not active.
- `429`: Too many requests from this IP address. The edge allows 100 requests a minute per client IP with a burst of 100, counted across every endpoint together, so sends and status checks share one budget. Accounts that ask for more are raised to 500 a minute; X-RateLimit-Limit on every response tells you which ceiling applies to you. Wait the number of seconds in Retry-After and repeat the request. Write to support@myotp.app with the addresses you call from and the peak you are designing for if you need a higher limit.
- `500`: The payment settled but crediting failed. Nothing is lost: write to support@myotp.app and the credits are applied from the payment ledger.

---

[API](https://skmtc.dev/myotp/apis/myotpapp-api.md) · [All operations](https://skmtc.dev/myotp/apis/myotpapp-api/llms.txt) · [OpenAPI document](https://skmtc.dev/myotp/apis/myotpapp-api/revisions/95ed911fdece?raw)
