Use the user billing endpoints to read your team’s credit balance and buy top-ups with a saved payment method. Both endpoints require authentication with your API key. Invalid keys return 402.
Check credit balance
GET /user/credits/info returns the authenticated team’s credit balance, a per-lot breakdown, active subscription details, and whether usage is allowed.
Use this endpoint to power billing widgets, usage dashboards, or pre-flight checks before running large jobs.
For API details see Get credit info.
import requests
response = requests.get(
"https://api.olostep.com/user/credits/info",
headers={"Authorization": "Bearer <API-TOKEN>"},
)
print(response.json())
Response
{
"credits": 12500,
"breakdown": [
{
"purchase_kind": "Subscription",
"allocated_units": 10000,
"remaining_units": 8500,
"expiry_date": 1735689600
},
{
"purchase_kind": "Top-up",
"allocated_units": 5000,
"remaining_units": 4000,
"expiry_date": 1743465600
}
],
"active_subscription": {
"id": "SUB_PRO",
"display_name": "Pro",
"credits": 10000,
"created_at": 1704067200
},
"allow_usage": true
}
| Field | Description |
|---|
credits | Total remaining credits across all non-expired lots |
breakdown | Per-lot detail: type, allocated and remaining units, expiry |
active_subscription | Current plan (falls back to SUB_BASE if none is active) |
allow_usage | Whether the team can still consume credits |
Each lot in breakdown has a purchase_kind of Subscription, Top-up, Manual, Setup, or Pending.
Purchase a top-up
POST /user/purchase-topup charges a saved card on Stripe and buys credits in one step. There is no Checkout redirect or embedded payment UI.
Pass the credit amount in the request body:
| Field | Description |
|---|
credits | Number of credits to purchase |
Supported values: 10,000, 20,000, 80,000, and 100,000.
For API details see Purchase top-up.
import requests
response = requests.post(
"https://api.olostep.com/user/purchase-topup",
headers={
"Authorization": "Bearer <API-TOKEN>",
"Content-Type": "application/json",
},
json={"credits": 10000},
)
print(response.status_code)
print(response.json())
Requirements
- The team must have a Stripe customer with at least one saved card.
- Only one purchase attempt is allowed every 60 seconds per team. If you hit the cooldown, the response is 429 with a
Retry-After header.
Responses
200 — payment succeeded:
{
"success": true,
"payment_intent_id": "pi_xxx",
"credits": 10000
}
202 — payment is still processing. Credits are added once Stripe confirms the payment:
{
"success": true,
"status": "processing",
"payment_intent_id": "pi_xxx",
"message": "Payment is processing. Credits will be added once the payment is confirmed.",
"credits": 10000
}
Credits are issued by the Stripe webhook after payment confirmation, not directly from the HTTP response. Treat 200 as payment success; poll GET /user/credits/info if you need to confirm the updated balance.
Common errors
| Status | Error | When |
|---|
| 400 | missing_topup_selector | credits was not sent |
| 400 | invalid_credits | Credit amount is not in the allowed list |
| 400 | no_stripe_customer | Team has no Stripe customer |
| 400 | no_payment_method | No saved card on file |
| 402 | payment_failed | No saved card completed the charge |
| 429 | purchase_topup_cooldown | Another purchase was attempted within 60 seconds |
| 503 | payment_status_unknown | Ambiguous Stripe error; wait before retrying |