Skip to main content

TypeScript

The @giveitsmaller/sdk package is a client for the GISL (Give It Smaller) file compression and processing API that runs in Node.js and in the browser. Upload a file, apply operations (compress, convert, thumbnail, merge, archive), and retrieve the results.

Install​

npm install @giveitsmaller/sdk

Requires Node.js 18+ on the server. In the browser it needs no polyfills — see In a browser.

Authenticate​

In Node, every request is authenticated with an API key sent as a Bearer token. Get a key from the dashboard at giveitsmaller.com. Pass it when you create the client — the SDK sets the Authorization header on every request for you. The base URL defaults to https://api.giveitsmaller.com; the key can also come from the GISL_API_KEY environment variable or ~/.gisl/credentials.

In a browser, never use an API key. For your own web app, call GISL from your backend — see In a browser.

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

const client = await gisl.create({ apiKey: 'REPLACE_ME_API_KEY' });

Quickstart​

The SDK is file-first: you start from a file (.file(path) for one, .files([paths]) for many) and call operations on it — upload, workflow creation, and waiting are handled for you in one chain.

import { gisl, OptimizeFor } from '@giveitsmaller/sdk';

const client = await gisl.create({ apiKey: 'REPLACE_ME_API_KEY' });

// One file: upload → compress → wait → resolve the download URL, in one chain.
const result = await client
.file('./photo.jpg')
.compress(OptimizeFor.Balanced)
.run({ maxWait: '5m' });

console.log('Compressed file:', result.url); // pre-signed download URL

// Many files (fan-out) — the same chain over a list:
const many = await client
.files(['./a.jpg', './b.png'])
.compress(OptimizeFor.Balanced)
.run();

for (const artifact of many.artifacts) console.log(artifact.url);

That is the full round trip. Worth knowing:

  • Presets express intent. OptimizeFor.Size, .Balanced, and .Quality pick sensible options for the input's media type. See Compress for per-call options.
  • run() waits up to 10 minutes by default — override with maxWait. For live progress instead of polling, subscribe to progress events.
  • result.transport says how the run finished: 'sse' or 'polling'. baseUrl alone does not move the progress stream, so a client built with only baseUrl polls and emits one GislWarning (GISL_STREAM_HOST_NOT_DECLARED); pass environment or streamBaseUrl too. See Seeing which transport a run used.
  • Failures throw typed errors (GislApiError, GislValidationError, GislTimeoutError, …). See Errors.
Low-level escape hatch

File-first is the recommended path. For full control over the workflow payload — hand-built job DAGs with uploadSource / OperationType and explicit uploadFile / waitForWorkflow / getWorkflowDownloads — the raw wire client is available via client.createWorkflow({ jobs: [...] }).

Try it without an account​

gisl.anonymous() builds a client with no credential at all — no API key, no environment variable, no profile, no cookie — for the guest flow:

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

const client = await gisl.anonymous();

const result = await client.file('./photo.jpg').compress().run();
console.log(result.url);

What a guest may do is decided by the API, not the SDK. Today that is:

  • any file type, up to 10,000,000 bytes per file (single-shot upload; multipart needs an account);
  • video at most 60 seconds long. The length is measured when the upload is probed; until then the API answers the create with probe_pending, and the SDK simply re-creates (backing off from 1 s to 30 s) until it is accepted, for up to 900 seconds, after which the API accepts a video whose length it could not measure (the SDK makes one last create about 2 s past that bound). A probeTimeoutMs ends that wait at its own end with GislProbePendingError: no create is scheduled past it, and a late timer may start one at most 1 s past. The run's maxWait (10 minutes by default) ends it with GislTimeoutError. maxWait starts before the upload, so to ride out the whole bound set it to at least 15 minutes plus your upload time;
  • the compress, convert and thumbnail operations;
  • 30 credits per IP per rolling 24 hours, priced like a signed-in workflow; a request that would cost more than is left is refused whole.

A larger file is refused locally, before anything is sent, with GislFeatureRequiresAuthError, as is any method that needs an account (credits, checkout and its status, cancel, resume, retry, listing workflows…). Everything else outside the rule comes back from the API as a GislApiError: a longer video with errorCode ANONYMOUS_LIMIT_EXCEEDED, another operation with ANONYMOUS_OPERATION_NOT_ALLOWED, and a spent allowance with ANONYMOUS_QUOTA_EXHAUSTED. The SDK carries the workflow's read token for you, so run() polls and downloads with nothing extra. gisl.create() never falls back to guest mode: without a key it still throws GislMissingCredentialsError.

In a browser​

Never put an API key in browser code

Anything shipped to a browser is readable by whoever loads the page, and an API key authenticates as your account.

For your own web app, call GISL from your backend with an API key, and keep the browser talking to your backend. The API's CORS allows browser requests only from GISL's own web app, so a browser-side client on any other site fails at the first request. To show a result, pass the pre-signed output URL to your frontend and link or display it (<a href>, <img src>). A fetch() of it from your origin is blocked by the storage bucket's CORS.

First-party only: the browser entry and session-cookie auth​

The package also runs in a browser bundle (vite, webpack, Rollup, esbuild, Parcel) with no Node polyfills. It ships a browser exports condition, and @giveitsmaller/sdk/browser if your bundler ignores that condition. There it authenticates with the user's GISL session cookie. That cookie is issued for .giveitsmaller.com with SameSite=Lax, so this mode is for GISL's own web app: no other site can send it.

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

// GISL's own web app only — see above.
const client = await gisl.create({ useSessionCookie: true });

const file = (document.querySelector('input[type=file]') as HTMLInputElement).files![0];
const result = await client.file(file).compress().run({ maxWait: '2m' });

What works in the browser: .file(blob) and .files([...]) with Blob or File inputs (single and multipart uploads), every operation with .run() and .submit(), waiting over SSE or polling, and the output URLs on RunResult.url and RunResult.artifacts[].url. With useSessionCookie: true every request is sent with credentials: 'include'.

Node-only, not available in the browser entry:

  • A path string passed to client.file('./photo.jpg'), .files([...paths]) or client.uploadFile('/path') throws. Pass a Blob or File instead.
  • RunResult.downloadTo() / toFile() throw GislSinkError. Use the output URL instead.
  • HttpDownloader and verifyWebhook are not exported from the browser entry. Verify webhooks on your server.

The default @giveitsmaller/sdk type declarations still include verifyWebhook and HttpDownloader. For types that match the browser bundle, import from @giveitsmaller/sdk/browser, or add "customConditions": ["browser"] to your tsconfig.json so the package's browser condition picks the browser declarations.

Where next​

  • Installation — every language, side by side.
  • Quickstart — the end-to-end walkthrough.
  • Operations — compress, convert, thumbnail, merge, archive.
  • Workflows — how jobs, sources, and outputs fit together.
  • Client reference — client options, credential resolution, and every low-level method.
  • Errors — the error taxonomy and retry guidance.