Skip to main content

Try it safely

There is no test mode​

Some APIs offer test keys that return simulated data — a sk_test_ key that returns fake charges, for example. The Mocart API doesn't do this: there are no test-mode keys and no "live mode" flag. Every key generates real creatives and spends real credits.

This is deliberate, not an oversight. A simulated run would have to return fake creatives, and Mocart's product rules treat test data in production collections as something to avoid entirely — there's no isolated sandbox to write fake rows into. A "do not actually charge" branch at the same code path that charges real accounts is also a standing risk of its own: it's one more thing that can go wrong at the exact place money moves.

The estimate is the dry run​

POST /runs/estimate (estimateRun) is the safe way to try a request before it costs anything:

  • It takes the same body you'd send to POST /runs (createRun).
  • It validates that body with the same rules createRun would apply — a shape error, a missing field, a product with no usable image, all surface here exactly as they would on create.
  • It writes nothing: no run is created, no credits are held, no Idempotency-Key is consumed. Call it as many times as you like.

A 200 from estimateRun means: this exact body would be accepted by createRun, at this price. That's the whole guarantee, and it's a strong one — every shape problem in your request surfaces on the call that costs nothing, before you ever spend a key's one shot at an idempotency claim.

curl -s -X POST https://api.mocart.io/api/public/v1/runs/estimate \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"brandId": "brand_9c1e2f7a4b3d",
"presetId": "preset_studio_softbox",
"productIds": ["prod_8f2a1c9b4d6e"],
"modality": "image",
"aspectRatio": "1:1",
"creativesPerProduct": 2
}'
{
"object": "run_estimate",
"estimate": { "perItemCredits": 4, "totalCredits": 8, "rateCardVersion": "2026-08-01" }
}

If your account has a daily spend budget, the response also carries it:

{
"object": "run_estimate",
"estimate": { "perItemCredits": 4, "totalCredits": 8, "rateCardVersion": "2026-08-01" },
"budget": {
"capCredits": 2250,
"spentCredits": 2246,
"remainingCredits": 4,
"resetsAt": "2026-09-24T00:00:00.000Z"
}
}

Check budget.remainingCredits against estimate.totalCredits before you commit — here remainingCredits (4) is less than totalCredits (8), so POST /runs would refuse this exact run with 409 ACCOUNT_SPEND_CAP_EXCEEDED. budget is absent (never null, never -1) when the account has no daily cap.

If the run doesn't fit the budget​

POST /runs refuses a run that doesn't fit what's left of the account's daily budget, before anything is charged and before your Idempotency-Key is consumed:

codestatusMeaning
ACCOUNT_SPEND_CAP_EXCEEDED409The quote doesn't fit what's left of the budget. detail names the budget, what's used, what's left, the quote, and the reset instant — the same numbers as the typed budget object above.
ACCOUNT_SPEND_UNAVAILABLE503The budget couldn't be checked at that moment — not a statement that it's already spent. Carries Retry-After; safe to retry with the same key.

Both refusals leave the Idempotency-Key unclaimed — reuse it once you've waited for budget.resetsAt or reduced the request to fit. The account owner can raise the daily cap, up to the plan's default, with PUT /credits/api-spend-cap from a signed-in dashboard session — an API key can't call it.

maxCredits: a hard ceiling on what one run may spend​

Pass maxCredits on POST /runs to cap what that run is allowed to cost, independent of any account-level budget:

  • maxCredits (optional, on create) — a hard ceiling on what this run may spend, checked twice, not only at create:
    • At create. If the quoted price exceeds maxCredits, the create is refused with 409 PRICE_EXCEEDS_MAX before anything is charged and before your Idempotency-Key is consumed. The run is never silently shrunk to fit — you get exactly what you asked for, or a refusal.
    • Per item, as the run executes. Each item is checked against maxCredits as spend accumulates. An item that would push the run past the ceiling is neither charged nor produced — it gets a row with SPEND_CEILING_REACHED instead.

A first call against a new key is a good place to set maxCredits equal to the quoted totalCredits — the run either completes exactly as quoted, or doesn't start:

{
"brandId": "brand_9c1e2f7a4b3d",
"presetId": "preset_studio_softbox",
"productIds": ["prod_8f2a1c9b4d6e"],
"modality": "image",
"aspectRatio": "1:1",
"creativesPerProduct": 2,
"expectedTotalCredits": 8,
"maxCredits": 8
}

expectedTotalCredits is a companion assertion: pass the totalCredits you got from estimateRun here, and if the rate card moved between your estimate and your create — even to a cheaper price — the create is refused with 409 PRICE_CHANGED rather than silently charging a different amount than you agreed to.

Both checks happen before any charge and before your key is claimed, so a refusal here costs nothing and the same Idempotency-Key is safe to reuse once you've adjusted the value.

Putting it together for a first real run​

  1. Build your request body.
  2. Send it to estimateRun. Confirm the price and any budget headroom.
  3. Send the same body to createRun, adding Idempotency-Key, expectedTotalCredits (from step 2), and maxCredits set to that same number.
  4. The run either completes at exactly the price you saw, or refuses before spending anything.

See the Quickstart for this as a complete, runnable sequence, and Runs for what happens after create.