Skip to main content

Troubleshooting

Symptom first. Each entry lists the failure you see and its likely causes, most likely first. For what each error class carries, see Errors.

401 on every request​

The API key is missing or malformed.

  • Pass the key as apiKey when you create the client. The SDK sends Authorization: Bearer <apiKey> itself; do not prefix the key with Bearer , or the header becomes Bearer Bearer … and every call fails.
  • If you set custom headers yourself, the SDK does not add Authorization for you unless apiKey is also set.
  • Check the key has not been revoked.

413 when uploading a large file​

Files above the multipart threshold (10 MB by default) upload in parts, and the first part is always exactly 8 MB. The SDK floors the threshold at 8 MB for that reason, so a threshold you set lower is raised to 8 MB rather than producing a malformed upload.

A 413 on a file below the threshold is not a multipart problem. Read the error code: image_dimensions_too_large means the image's pixel area is over the cap for raster images, whatever its size in bytes; otherwise it is your plan's per-upload limit, so check your account limits.

GislValidationError when creating a workflow​

The payload does not match the operation's schema. details[] names each field and what is wrong with it. getSchema() returns the options every operation accepts for each media type. The usual causes:

  • An option that does not exist for that operation or media type, or one that exists only on some routes. Image compress has no mode or icc_profile: metadata is controlled by metadata (strip, the default, or keep, which preserves EXIF, colour profile and XMP on a same-format compress).
  • A convert target format the input media type cannot be converted to.
  • merge and archive built by hand with a single source: they take a job with inputs[]. The file-first merge(...) and files(...).archive(...) build that for you.

The SSE stream ends without a terminal event​

A proxy timeout, a sleeping laptop, or a dropped connection closed the stream. The server closes it silently, so iteration simply ends rather than throwing.

  • If you need the final state for certain, fetch it once the stream closes with getWorkflowStatus(workflowId).
  • run() handles this for you: it streams over SSE and falls back to polling if the stream drops. See Progress streaming (SSE).

SSE events arrive in bursts​

Something between you and GISL is buffering the response: a corporate HTTP proxy or an edge rewrite rule. GISL sends Cache-Control: no-transform, but some edge configurations strip it. Confirm by opening the stream from outside the proxy. There is no client-side fix.

Webhook verification fails​

The body you verify is not the bytes GISL signed. Your framework almost certainly parsed the JSON first. Verify the raw body, then parse it. If the raw body is right, the secret is wrong: it belongs to another workflow, or the workflow was re-created. See Webhooks.

Invalid signature format means the sha256= prefix is missing from the header value you passed. Pass the header value unchanged.

A request times out during a large upload​

timeout (default 30 seconds) applies to each request to the GISL API, such as multipart initiate and complete. It does not cover the part uploads to storage. Raise it for very large or metadata-heavy files rather than globally.

To cap the whole upload, pass an AbortSignal driven by your own timer. Aborting cancels the API requests and every part upload, and the upload rejects with GislAbortError:

import { GislAbortError } from '@giveitsmaller/sdk';

const controller = new AbortController();
const cap = setTimeout(() => controller.abort(), 5 * 60_000); // 5-minute ceiling

try {
await client.uploadFile('./huge-video.mp4', { signal: controller.signal });
} catch (err) {
if (err instanceof GislAbortError) {
// clean up, or surface it to the caller
}
throw err;
} finally {
clearTimeout(cap);
}

Part uploads retry transient failures (5xx, 429, network errors) up to multipartMaxAttempts attempts in total (default 3) with jittered exponential backoff from multipartRetryBaseMs (default 500 ms). Other 4xx responses and aborts fail at once. When the attempts run out, the upload fails with GislMultipartPartError, which carries partNumber and uploadId. Set multipartMaxAttempts: 1 to turn retries off.

GislTimeoutError while waiting for a workflow​

The workflow did not reach a terminal status within the wait deadline. The low-level waitForWorkflow() waits 10 minutes by default in both SDKs; pass a longer timeoutMs for heavy inputs such as long videos or large archives. run() takes its own maxWait.

A timeout ends your wait, not the workflow. Do not re-run it: the work may still complete. Check it with getWorkflowStatus(), or recover the result as described in Recovering a timed-out run().

CORS errors in the browser​

The API's CORS allows browser requests only from GISL's own web app, so calling the API directly from your site is blocked. Run the SDK on your backend and have your frontend call your own API. Downloading an output URL with fetch() from your origin is also blocked by the storage bucket's CORS; link to it or download it server-side. See TypeScript SDK.

TypeScript: type errors on OperationDef.options​

In the low-level API options is Record<string, unknown>. Check an options object against its typed interface without a cast using satisfies:

import { OperationType, type CompressImageOptions } from '@giveitsmaller/sdk';

const op = {
type: OperationType.compress,
options: { quality: 80 } satisfies CompressImageOptions,
};

The ergonomic builders' option bags are typed already: client.compress(input, options) and client.file(...).compress(optimize, options) take CompressOptions, and a misspelled key is a compile error.

No webhookSecret on the created workflow​

No callback URL was set. The secret is only generated when the workflow has one. Re-create the workflow with a callback URL if you need it.

Downloads come back empty​

The workflow has not reached a terminal status, or its jobs failed and produced nothing. Check getWorkflowStatus() first. A failed workflow has no outputs; in a partially_failed one, the jobs that succeeded have downloads and the failed ones do not.

Still stuck?​

Contact support with: the SDK and runtime versions, the workflow id, the request path, the full error message and stack trace, and a minimal payload that reproduces it.