Troubleshooting and recovery
Find your symptom, see why it happens and what to do — payments, 401 and 403, balance and limits, unavailable models, failed or stuck tasks, uploads, callbacks and streams.
Each section below starts from what you see, explains the usual cause and tells you what to do. For the full meaning of every error number, see Errors; this page is the "what now?" companion.
Find your problem
| What you see | Go to |
|---|---|
| I paid but my balance didn't change | Paid, but the balance didn't change |
Every call returns 401 or 403 | Every call returns 401 or 403 |
40201 or 40202, or 429 | Balance, limits and support |
A model is missing, or you get 50301 or 40004 | Model missing or unavailable |
400 or 40901 when creating a task | Invalid input or quote conflict |
| The request timed out and you have no task ID | Submission timed out without a task ID |
The task ended as failed | The task failed |
The task stays queued or running for a long time | Task stays queued or running |
| An upload worked, but the task rejects it | Upload succeeded but input is rejected |
| No callback arrived, or the result link or file is gone | Missing callback or expired download |
| A text stream stops before the end | Stream ends early |
| In a team, you can't create a key, see prompts or accept an invitation | Team permissions |
| You can't issue an invoice | Can't issue an invoice |
| A response isn't JSON at all | Keep useful evidence |
Keep useful evidence
Before changing anything, write down what happened. It makes every step below faster, and it is what support needs:
- The time, in UTC if you can.
- The endpoint you called, for example
POST /api/v1/jobs/createTask. - The HTTP status and the
codefrom the response body. - The
request_id: it is in the response body and in theX-Request-Idresponse header. - The task ID, if you got one. It starts with
job_and is also listed on the console Logs page.
Keep three situations apart, because they call for different actions: the API explicitly refused the request (you got a JSON error — nothing was created or charged), you got no response at all (a timeout or dropped connection — the task may exist; see below), or the task was accepted and failed later (you have a task ID with state: "failed").
If a response isn't JSON — for example an HTML or plain-text page — it came from our edge network before reaching the API. Note the HTTP status and the first lines of the body. A 403 page of this kind usually means the request comes from a region we can't serve; retrying won't change it.
Never put API keys, passwords, full prompts or original files into general logs or support requests.
Paid, but the balance didn't change
- Card: the balance changes only after the payment processor's confirmation reaches us, usually within seconds. Refresh Billing and check Top-up orders.
- Crypto: the balance is added after the network confirms the transaction, usually a few minutes. Paying less than the amount shown, or after the payment page expired, is not added automatically.
- Manual transfer: open the order under Top-up orders and submit your bank reference or transaction hash. We check it by hand, usually within a few hours.
The full list of cases is in Account setup → Common problems. Still missing? Open a Support conversation (topic Billing) with the order ID and, for crypto, the transaction hash. Never send card numbers or passwords.
Every call returns 401 or 403
Start with a call that is free and changes nothing — reading your balance:
echo "${SPICY_API_KEY:0:9}"
curl https://api.spicyapi.ai/api/v1/chat/credit \
-H "Authorization: Bearer $SPICY_API_KEY"The first line should print sk-spicy-. If it prints nothing, the key isn't loaded in this terminal — set it again (see Account setup). Then read the msg of the response:
| You see | Cause | What to do |
|---|---|---|
401 Missing credentials: … | No key reached us | The Authorization header is missing. Check that the environment variable is set in the process that runs your code, and that a proxy or HTTP client isn't dropping the header |
401 API key format is not recognized: … | The value isn't a SpicyAPI key | You pasted something else — a console sign-in value or another service's key. Copy a key that starts with sk-spicy- |
401 Credentials are invalid or expired | The key doesn't exist, was revoked or passed its expiry date | Look at the key on the API keys page. An expired key gives the same answer as a revoked one. Create a replacement if needed |
40310 | The key is fine, but the account's email address isn't verified yet | Open the verification link we emailed you, or send a new one from the console banner, then retry. No new key is needed. See Errors |
403 This account cannot call the API right now | The key is fine, but the account is restricted | Contact support with the request_id |
40301 | This key isn't allowed to call this model | Edit the key and add the model to its allowed models, or use another key |
40302 | Your server's address isn't on the key's IP allowlist | Add the address to the key, or remove the IP restriction |
40303 | The service isn't available in your region | This can't be worked around. See where we operate |
None of these improve with retries.
Balance, limits and support
40201— the available balance doesn't cover the task's estimated price. Add funds (see Account setup), then send the same request again with the sameIdempotency-Key. A promotional grant only pays for the models it covers.40202— a spend limit was reached;msgsays which. A key cap (This key has reached its …) is raised under API keys → Edit, or resets on its own (daily at 00:00 UTC, monthly on the 1st, lifetime never). A team limit (Workspace … budget has been reached) is raised on the Team page by an owner, admin or budget manager. The platform cap (Platform quota for today is exhausted…) can't be raised; retry after 00:00 UTC. See Spend caps.429— too many requests. Wait for the number of seconds inRetry-Afterand slow down; polling with webhooks instead of tight loops helps most. If the message isToo many pending uploads…, waiting won't help: finish the uploads you already started. See Rate limits.
Contacting support: open a conversation under Support in the console, or email support@spicyapi.ai. Include the evidence from above: time, endpoint, code, request_id or task ID, what you expected and what happened, and the structure of your request with private values removed. Never send an API key.
Model missing or unavailable
- The model isn't in your list, or you get
404: callGET /api/v1/modelswith your API key and use a model whoseenabledandavailableare bothtrue, copying its ID exactly. A model page on the website doesn't prove your key may call it right now. See Models. 50301— the model can't take tasks at the moment. Retry a few times with growing pauses, then stop; repeated requests can't open an unavailable model. Refresh the catalog, and ask the person paying before switching to another model.40004— the model is working, but not with this combination of settings. Themsgnames the setting in parentheses. Change that value (for example a resolution or aspect ratio) instead of retrying.40301— the model exists, but this key isn't allowed to use it; edit the key's allowed models.
Invalid input or quote conflict
400— compare yourinputwith the model's currentinputSchemain the catalog: field names, types, allowed values and conditional requirements. Remove fields the model doesn't list. Check again after switching models, because each model has its own schema.40003— an uploaded file doesn't match what you declared when requesting its upload URL. Request a new upload URL with the rightcontentTypeand size, and upload again.413— the request body is too large. Upload media through the media flow instead of embedding it.40901— the quote expired, the request changed after quoting, or the price changed. Get a new quote, show the new amount to the person paying, and confirm again. See Quotes.- Other
409— handle by operation. A key already used for a different payload needs a new, correctIdempotency-Keyfor the new request, not an endless retry. See Errors → When 409 is worth retrying.
Submission timed out without a task ID
A timeout doesn't tell you whether the task was created. Don't just send it again with a new key — that can create a second task and a second charge.
Resend exactly the same request
Use the same body and the same Idempotency-Key, with the same API key, within 24 hours. If the first attempt was accepted, you get the original task back and are charged once. If it wasn't, this creates it now. See Idempotency.
Read the task
Once you have the task ID, call recordInfo. Its state, cost and settled tell you the outcome.
Only then decide on a new submission
If the original failed, a new task with a new key is a new charge at current prices.
Client timeouts, closed browser tabs and disconnected streams don't cancel an accepted task, and an accepted task can't be canceled.
The task failed
Failed or expired tasks normally release the full hold. Some content refusals are charged at the accepted quote only when the model page explicitly discloses this exception. Check settled and cost for the final charge.
- Don't retry unchanged:
invalid_request,unsupported_combination,content_rejected(the selected model declined the content — change the prompt or the model). - Worth retrying after a pause:
rate_limited,upstream_unavailable,upstream_failed. - Fix first:
invalid_asset(upload the file again),timeout(try a shorter duration or lower resolution),generation_failed(the same inputs usually fail the same way).
errorMessage explains the reason for people; it may be the model's own reason in English. Branch your code on errorCode only. The full table is in Errors → Task failure codes. A retry is a new task and is billed again if it succeeds.
Task stays queued or running
Generation takes time — a long video can take several minutes. Every task has a server-side deadline, returned as deadlineAt in recordInfo:
- Before
deadlineAt: keep waiting for the callback, or pollrecordInfowith growing pauses (not every second). - After the deadline: the task ends on its own as
expiredwitherrorCode: "timeout", and its hold is returned in full. You don't need to cancel anything — and accepted tasks can't be canceled.
If a task stays running well past its deadlineAt, contact support with the task ID.
Upload succeeded but input is rejected
- You skipped the commit. A successful
PUTof the file is not enough: callfiles/{fileId}/commit, then pass the returnedspicy://URI in the model's input field. Never pass theuploadUrlitself. - The file doesn't match the ticket. Check the declared
Content-Type, size and format; a mismatch returns40003. - It has expired. An uploaded file can be used until the
expiresAtin its commit response — 1 day by default, or sooner if your account deletes uploads when the job ends. Retrying a task may therefore need a fresh upload. - It belongs to another account. A
spicy://URI only works with keys of the account that uploaded it.
See Media for the full upload flow.
Missing callback or expired download
No callback arrived:
- Check that
callBackUrlis anhttps://address reachable from the internet. Plainhttp://is not accepted. - Check that your receiver verifies the signature over the original request bytes, before any JSON parsing, and answers with a
2xxquickly. Slow work belongs in your own queue. If the signing key was rotated recently, the receiver must use the new one: there is no overlap period. See Webhooks. - Look at the delivery history under Webhooks in the console, where you can also retry a delivery.
- Meanwhile, read the task with
recordInfo— polling is always a valid fallback. See Webhooks.
Callbacks can arrive more than once or late, so deduplicate before fulfilling an order.
The download link or file is gone:
- A download link lasts 20 minutes. If the result is still retained, just request a new link; refreshing an old link doesn't work.
- If the output is still being copied to storage, wait a few seconds and ask again.
- Results are kept for about 14 days at most, and less if your account uses a shorter retention setting. Once
contentStateisexpiredorpurged, the file can't be recovered — run the model again. See Retention.
Stream ends early
Check the HTTP status and Content-Type before parsing the stream for your protocol. Network chunks are not complete events, and a closed connection is not a success marker: look for the protocol's final event, any error event, and your own application deadline. Don't assume a disconnected stream was free or can be resent at no cost — valid usage recorded before the disconnect may be billed. Keep the request_id and check your usage records. See Text and streaming.
Team permissions
- You can't create a key. The scope picker at the top of the console's left sidebar is set to a team where your role is Budget manager or Read-only. Creating keys needs the Owner, Admin or Developer role. Switch to your Personal scope, or ask a team admin for the Developer role.
- You can see a task but not its prompt or result. Budget managers and Read-only members see status, cost and usage only.
- You can't accept an invitation. Sign in with the exact email address the invitation was sent to. Invitations expire after 7 days; ask for a new one if yours has.
- A team key stopped with
40201. Team keys spend the team owner's balance, so the owner needs to add funds.
See Console and teams for roles and team limits.
Can't issue an invoice
- Your details are incomplete. An invoice needs a name (your company or your own), address line 1, city and country, entered once at the top of Billing → Invoices.
- A character can't be printed. Only Latin, Greek and Cyrillic characters can appear on an invoice. Write a Japanese or Korean company name in its registered romanized form.
- It already exists. Each paid top-up and each closed month can be invoiced once; download the existing document. To correct details, choose Reissue on it.
- The month isn't available. Only months that have ended and in which you spent something can have a usage statement.
See Billing → Invoices and usage statements.
Related workflows
Errors · Production integration · Idempotency · Media · Webhooks · Text and streaming · Account setup

