Skip to content

Troubleshooting

2DAITeam edited this page Oct 11, 2026 · 6 revisions

Troubleshooting

Client-side error codes

These come from the SDK itself (httpStatus is 0), before or instead of a server response:

Code Meaning Fix
MISSING_API_KEY new Client({...}) without an apiKey Pass your 2dai_sk_… key
NO_FETCH No global fetch in this runtime Node ≥18, or pass fetch in ClientOptions
NETWORK_ERROR The request never reached the API (DNS, offline, TLS) Check connectivity / baseUrl
TIMEOUT One HTTP call exceeded timeoutMs (default 60 s) Raise timeoutMs, check network
ABORTED Your AbortSignal fired mid-request Expected — you cancelled
CLIENT_TIMEOUT waitFor/blocking generate hit its wait deadline Raise timeoutMs in SubmitOptions/WaitOptions; the job may still finish server-side
BAD_RESPONSE 2xx with a non-JSON body (proxy in the way?) Check baseUrl points at the API, not a web page
NO_CDN_ID cdn.* couldn't extract a CDN id from the argument Pass a Creation/result object or the id string
NO_CREATION_ID A creations.* op couldn't resolve a creation id Same idea
UNSUPPORTED Node-only option used in a browser (path, savePath) Use data/base64 and toBlob/toDataUrl
HTTP_<status> Server error without a machine code Inspect err.httpStatus / err.details

Server-side codes (INSUFFICIENT_CREDIT, KEY_SPEND_LIMIT_REACHED, MISSING_SCOPE, NSFW_REJECTED, …) are covered in Errors.

Browser usage notes

  • Never ship a key in public client code — anyone reading your bundle can spend your credit. Browser use is for internal tools/dashboards you control.
  • CORS: the API allows browser calls with the Authorization header; if you proxy it, forward that header.
  • uploads.image({ path }) and download(..., { savePath }) are Node-only (they throw UNSUPPORTED); use { data: file } from an <input type="file"> and cdn.toBlob() / cdn.toDataUrl() instead.
  • cdn.url() loads in an <img> only for public creations — private ones need the key, so go through toDataUrl/toBlob.

Retries, rate limits & pagination

  • Idempotent GETs auto-retry on 429/5xx up to maxRetries (default 2) with exponential backoff, honouring a server Retry-After header. Generation submits never auto-retry (they may charge); the server de-duplicates YOUR immediate retry of an identical call by itself (~5 s window). Pass a hex clientToken ([0-9a-f]{1,64}) to set the window.
  • A RateLimitError that survives the retries means sustained pressure — space out your calls.
  • creations.list returns at most 100 rows per call (limit clamped to 1..100, default 20). Page with the cursor:
let before: string | undefined;
do {
  const page = await client.creations.list({ limit: 100, beforeDate: before });
  handle(page.creations);
  before = page.nextBeforeDate;
} while (before);

The generation seems stuck

generate.* waits up to timeoutMs (default 5 min). Heavy qualities can be slow at peak times, and when all render capacity is busy a job waits in the queue (pending) until capacity frees up — it ends expired and is refunded if none does before its queue deadline. If your wait times out, the job usually still completes:

const state = await client.queue.get(queueId);   // 'processing' | 'completed' | …

generate.similar rejects my creation

Two kinds of rows can't be re-derived, by design: raw uploads (nothing was generated, there are no parameters to replay) and wallpaper-resize outputs (their pricing dimension isn't stored on the creation). Both come back as a 400 ValidationError with REGEN_UNSUPPORTED_TOOL. REGEN_MISSING_SOURCE / REGEN_MISSING_PROMPT mean the stored row lost what its tool needs — usually because a reference was permanently deleted. Re-submit through the normal generate.* method instead.

An old queueId 404s

It shouldn't: settled queue rows are archived (never deleted) once they age past your newest ~200, and queue.get reads the archive transparently. A NotFoundError on a stored id means the id is wrong or belongs to another account — not that it expired.

Clone this wiki locally