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.Qualitypick sensible options for the input's media type. See Compress for per-call options. run()waits up to 10 minutes by default — override withmaxWait. For live progress instead of polling, subscribe to progress events.result.transportsays how the run finished:'sse'or'polling'.baseUrlalone does not move the progress stream, so a client built with onlybaseUrlpolls and emits oneGislWarning(GISL_STREAM_HOST_NOT_DECLARED); passenvironmentorstreamBaseUrltoo. See Seeing which transport a run used.- Failures throw typed errors (
GislApiError,GislValidationError,GislTimeoutError, …). See Errors.
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). AprobeTimeoutMsends that wait at its own end withGislProbePendingError: no create is scheduled past it, and a late timer may start one at most 1 s past. The run'smaxWait(10 minutes by default) ends it withGislTimeoutError.maxWaitstarts before the upload, so to ride out the whole bound set it to at least 15 minutes plus your upload time; - the
compress,convertandthumbnailoperations; - 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
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])orclient.uploadFile('/path')throws. Pass aBloborFileinstead. RunResult.downloadTo()/toFile()throwGislSinkError. Use the output URL instead.HttpDownloaderandverifyWebhookare 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.