New: Nano Banana 2.1Try the model
API Stock

Operations

Going to production

Key handling, webhooks over polling, idempotency, terminal states, retry policy by error code, balance monitoring, rate limits and media storage.

10 min read14 sections

Before launch, check API-key storage, task polling, duplicate webhooks, retries, balance alerts and media downloads. Use the checklist below and run the bounded client examples before sending production traffic.


Run a bounded Chat Completions integration

Checked 19 October 2026, Python 3.14.4, standard library. Download the complete client. It sends one non-streaming request to /api/v1/chat/completions, limits the response to 32 tokens and prints the answer and usage. Keep API_STOCK_KEY in the server environment; the script never saves it. Run:

sh
export API_STOCK_KEY="your-server-key"
python3 chat-completions.py

The example uses GPT-6 Luna. At the checked public tariff, input costs $0.12 and output $0.60 per million tokens; this tiny request is below $0.001. Check the current model table before changing the prompt or limit. HTTP errors and timeouts stop the script; a lost response does not prove that no tokens were billed, so it does not retry automatically.

This is a Chat Completions HTTP client. It does not claim compatibility with every OpenAI endpoint, streaming SDK feature or media-generation request. For asynchronous media use the Suno example with a saved task ID instead. API authentication documents keys.

API keys

  • Send the key in a header, never in a query string. Both Authorization: Bearer sk-… and x-api-key: sk-… are accepted. Query strings end up in proxy logs and browser history.
  • Never ship a key to a browser or a mobile binary. The public API has no CORS-safe, scoped, or short-lived key type — a key is full account access and full spend authority. Proxy generation calls through your own backend.
  • Issue one key per environment and per service. Staging, production and each background worker should be separable, because revocation is per key. The only way to answer "what was this key doing" is to have kept them apart.
  • Rotate by overlap, not by cutover. Create the new key, deploy it, confirm traffic has moved, then deactivate the old one. Deactivation takes effect on the next request — successful key lookups are cached for an hour, but every mutation that revokes a key drops that cache entry.
  • Store keys in a secret manager, not in .env files committed to the repository. Treat a leaked key like a leaked credit card: deactivate first, investigate second.

Prefer webhooks over polling

Pass a webhook URL on create and the finished task is POSTed to you. The delivered body is byte-identical to the task-status envelope — the same { code, data: { taskId, status, files, output, createdTime, errorMessage } } you would have polled for. One code path handles both.

json
{
"model": "veo3.1-fast",
"input": { "prompt": "…", "aspectRatio": "16:9" },
"webhook": "https://example.com/hooks/api-stock/8f2c1e9a-b40d-4c77-9a1e-secret"
}

Operational notes:

  • There is no signature header. Authentication is whatever you put in the URL. Use a long random path segment or query parameter, compare it in constant time, and treat the endpoint as public otherwise.
  • Never trust the payload as authorisation. Look up the taskId in your own database before acting on it; a body claiming a task you never submitted is not your task.
  • Return 2xx fast. Delivery is retried 13 times with exponential backoff starting at 10 seconds — about 22 hours of attempts. Anything outside the 200–299 range counts as a failure. Enqueue the work, then respond.
  • Expect duplicates. A response that times out after your handler committed still counts as a failure and will be redelivered.

Details in Webhooks.


Polling, when you must

Some environments cannot expose an inbound endpoint. Polling GET /api/v1/task/status/{taskId} is fine — just do it on a budget.

  • Match the interval to the medium. Images finish in seconds; video and music in minutes. Roughly 3 s for images, 8–10 s for audio and video. Sub-second polling buys nothing but rate-limit pressure.
  • Back off on repeated processing. Start at the interval above and grow it — for example 5 s, 10 s, 20 s, capped at 30 s. A render that has taken two minutes is unlikely to land in the next 500 ms.
  • Set a wall-clock deadline, and treat it as your timeout, not the task's. The generation keeps running server-side after you stop polling. Persist the taskId and resume later rather than resubmitting.
  • Poll from a worker, not from a request handler. A user-facing request that blocks for four minutes is a bug regardless of what the API does.
  • Back off hard on 429. See Rate limits.

Idempotency

The create endpoint has no idempotency key. taskId is your idempotency anchor — everything downstream keys off it.

  • Persist the taskId in the same transaction that records the intent to generate. If your process dies between the HTTP call and the write, you have paid for a result you can never collect.
  • Make webhook and poll handlers idempotent on taskId. Both paths can deliver the same terminal state, and the webhook can deliver it more than once. An upsert keyed on taskId is the whole solution.
  • Never resubmit to "check" on a job. A second create is a second generation and a second debit. Query the taskId you already have.
  • Guard user-triggered retries. Debounce the button, or key the request on something stable in your own domain so a double click cannot become two charges.

Handle every terminal state

Four statuses, and only two are terminal:

StatusTerminalMeaning
not_startednoqueued, not yet dispatched
processingnoin flight at a provider
finishedyesresult available
failedyesexhausted, refunded
  • Branch on status first. finished is terminal and ready even when files is absent; never keep polling a finished task.
  • Inspect the result after finished. Media actions return files, while data-producing actions — Suno lyrics, timestamped-lyrics, bpm, and Midjourney describe — return output without files.
  • Read all of files. A Seedream batch or a Suno music action returns several entries; taking files[0] throws away results you paid for.
  • Read errorMessage on failed and store it. By the time a task is failed, the platform has already retried the same provider up to its attempt limit, walked its reserve providers, and refunded the debit. Nothing further will happen to that task.
  • Give yourself a stuck-task path. A task that has been processing far past the model's normal runtime should page a human, not loop forever.

Retry, or do not retry, by error code

Errors always arrive in one shape:

json
{
"code": 402,
"error": {
"message": "Insufficient balance. Please top up your account",
"type": "PaymentRequired",
"code": "insufficient_balance"
}
}

Use both the HTTP status and error.code. The code is stable when present, but rate-limit responses currently use unknown_error; detect them from status 429. error.message is English prose for logs and may be reworded.

error.codeHTTPRetry?
invalid_input400No — fix the body first
api_key_missing401No — fix the client
api_key_invalid401No — key revoked, inactive or banned
api_key_not_found404No
auth_required401No
insufficient_balance402Only after topping up
task_not_found404No — wrong id or wrong account
generation_not_found404No
not_found404No — check the path
unknown_error with HTTP 429429Yes — after Retry-After
unknown_error500Yes, with backoff and a cap

Rules that follow from the table:

  • 4xx is a client bug except for 429 and 402 insufficient_balance. Retrying an unchanged 400 body produces an identical 400 forever. A 429 is transient: wait for Retry-After, add jitter, and retry with a cap. Retry insufficient_balance only after replenishing the balance.
  • 5xx and transport errors are worth retrying with exponential backoff and jitter — three or four attempts, then park the job.
  • A create call that times out on your side may still have succeeded. Do not blind-retry it. Reconcile against your own record of the intent before spending again.
  • failed tasks are not errors to retry automatically. The platform already did. A content-policy rejection in particular is terminal and will fail identically on every provider.

Full list in Errors & error codes.


Watch the balance

Billing is prepaid. The balance is debited at create time, not on completion, and refunded automatically when a task ends in failed.

  • Check the balance before a batch, not after the 402.

    sh
    curl https://api.api-stock.com/api/v1/user/me \
    -H "Authorization: Bearer sk-your-key"
    json
    {
    "code": 200,
    "data": {
    "id": "019c1f70-2b44-70d3-9c22-6b7ad0f41e18",
    "balance": 48250,
    "createdAt": "2026-01-14T09:03:11.000Z"
    }
    }

    balance is in integer cents — 48250 is $482.50. Do not divide it in a float and compare for equality.

  • Alert on a threshold, not on empty. Pick a floor that covers a day of normal spend and top up when you cross it. A 402 in production is a queue of failed jobs, not a warning.

  • Track spend over time with GET /api/v1/stats — aggregated usage and spend per period, model, type and day. Use it to spot the model whose price changed, or the retry loop that quietly tripled your bill.

  • Reconcile against your own records. Your task table plus the price you expected should match what the stats endpoint reports. A divergence usually means duplicate submissions.

See Pricing & balance.


Rate limits

The API allows 120 requests per 60 seconds per client IP and endpoint. A create route and a status route use separate buckets, while all callers behind one egress IP share the bucket for any one route.

  • Budget the polls, not just the creates. A hundred in-flight jobs polled every 3 seconds is 2000 requests a minute from one IP. This is the usual way to hit the ceiling.
  • Use a separate limiter bucket per endpoint in your own code, keyed by the egress IP when your workers can use more than one.
  • Prefer webhooks when the fleet grows. They remove the polling traffic entirely and the limit stops being the binding constraint.
  • Remember the limit is also per IP. Every worker behind one NAT gateway shares each endpoint's bucket; workers on separate egress IPs do not.

See Rate limits.


Copy the media into your own storage

Every fileUrl in files points at platform storage and is valid for 24 hours.

  • Download on completion, in the same handler that records the terminal state. Do not defer it to a nightly job.
  • Never hot-link a fileUrl from your product. It will 404 for your users a day later, and you will not find out until they tell you.
  • Store your own copy plus the original URL and the taskId, so a failed download is diagnosable and repeatable while the URL is still alive.
  • Verify the download. A truncated video that nobody checked is worse than a missing one, because it looks like a success.
  • URLs inside output are not mirrored. Suno cover art, for example, points at the provider's CDN and has its own lifetime. Copy anything you intend to keep.

See Files & storage.


Logging and support

  • Log the taskId for every generation, always. It is the primary key of every support conversation, and without it a question about a bad render is unanswerable.
  • Log the request model and input alongside it. Reproducing a failure needs the exact body, and input shapes differ per model.
  • Log error.code and error.message verbatim on failures, and errorMessage from failed tasks. The message is stable English, safe to log and safe to grep.
  • Do not surface raw provider errors to end users. errorMessage is already the sanitised, user-facing text; internal provider strings are not exposed by the API.
  • Emit a metric per terminal state. A rising failed rate on one model is the signal that something upstream changed.

Validate input before you spend

GET /api/v1/catalog returns every public model with its display metadata, price table and parameter schema derived from that model's input DTO. It is anonymous — no key required.

sh
curl https://api.api-stock.com/api/v1/catalog

A single model:

sh
curl https://api.api-stock.com/api/v1/catalog/veo3.1-fast

Use it to:

  • Validate a form client-side against the same enums and ranges the API enforces, so an impossible resolution never becomes a 400.
  • Discover new models without a deploy. The catalog is the source the public site itself renders from; a model appears there as soon as it exists.
  • Detect drift. If a hard-coded enum in your client no longer matches the catalog, that is your notice to update — before a user finds it.

Do not treat the catalog as a substitute for handling 400s. It is a convenience, not a contract; the DTO is the contract.


The short version

  • One key per environment; rotate by overlap; never in a browser.
  • Webhooks first, polling with backoff second, and a secret in the webhook URL.
  • taskId is the idempotency key — persist it before you need it.
  • Branch on status, then on files versus output.
  • Retry 5xx, never 4xx, and never a failed task.
  • Alert on a balance floor, not on 402.
  • Stay under 120 requests per 60 seconds, polls included.
  • Copy media within 24 hours; log the taskId forever.

Next steps

Run this with your own key

Choose a model in the API Stock catalog, check its price and try it in your browser or through the API.

Keep reading