Amniscient Detection SDK for TypeScript
TypeScript/JavaScript client for the Amniscient Inference API over HTTPS or
NATS (WebSocket): low-latency object detection behind a single call,
client.detect(image, modelId).
Requires Node 22+ (native fetch and WebSocket) or a modern browser. One
runtime dependency: @nats-io/nats-core, for the NATS transport.
Installation
Quick start
Option A — the snippet. Drop this into your app:
Option B — a runnable script. Save this as detect.ts, fill in the four
values at the top, and run it with npx tsx detect.ts path/to/image.jpg:
Top-level
awaitneeds"type": "module"in yourpackage.json(or name the filedetect.mts). A ready-to-run version that reads these from env vars lives inexamples/basic-detection.ts.
detect() accepts a file path (Node), raw bytes (Uint8Array/ArrayBuffer),
or a Blob (browser). Everything is async — the client is safe to construct
once and reuse.
Endpoint. Use the HTTPS endpoint
https://api.amniscient.com/inference. YourapiKeyis your own application API key — validated by the API gateway on every request. (This SDK is HTTP-only. For NATS, use the Python SDK,amniscient-detection0.5.0 or later.)
How it works
The whole surface is detect(). You give it an image and a model; it returns a
DetectionResult. Everything in between is handled for you:
- Auth is validated first — a missing API key throws
AuthenticationErrorbefore any image work or network call happens. It is never skipped. - The image is prepared — loaded into bytes, optionally compressed, and validated.
- The request is sent over HTTPS or NATS and, on a transient failure (network error, timeout or 5xx), retried with exponential backoff.
- The reply becomes a
DetectionResult, or a specific typed error is thrown (see Error Handling).
For contributors, the pieces are small and single-purpose: client.ts orchestrates
the flow and owns retries + error mapping; transport.ts moves bytes over HTTP or
NATS and nothing else; contract.ts is generated from sdk/contract/contract.json; types.ts holds the public shapes plus
the filterBy* helpers; utils.ts is where a reply becomes a DetectionResult
and where image bytes are loaded/validated; annotate.ts turns a payload into
draw-ready boxes; errors.ts holds the typed error hierarchy and the HTTP-status
mapping.
Features
- One call,
detect()— connection handling, retries, and parsing done for you - HTTPS or NATS — the same calls over either; NATS keeps one WebSocket open for lower latency
- Built on native
fetch/FormData/Blob/WebSocket— Node 22+ or a modern browser - Pluggable compression — passthrough by default; supply a
compresshook (e.g. backed bysharpor a<canvas>) to actually resize/re-encode - Retry logic — exponential backoff for transient errors (network, 5xx)
- Validated auth — a missing/invalid API key throws
AuthenticationError, never a silent failure - Fully async — every method returns a
Promise; fan out withPromise.allwhen you need concurrency - Raw mode —
detectRaw()returns the unparsed server payload when you need it - Full type hints —
.d.tsshipped for every export, IDE-friendly
Usage Examples
Different image inputs
Filtering results
Each filterBy* helper returns a new DetectionResult — the original is left
untouched.
Raw responses
By default detect() parses the server reply into a DetectionResult. Use
detectRaw() when you need the unparsed response instead — for logging/debugging,
or when downstream code already depends on the original xywh contract:
Retries, error mapping, and auth validation are unchanged — only the return value skips parsing.
Barcode & QR reading
Pass detectBarcodes: true to decode barcodes/QR codes alongside object
detection, in the same request:
It’s off by default — pass detectBarcodes: true explicitly. Supported
symbologies (wire type values): qr, upc-a, upc-e, code-39.
Notes:
result.barcodesis[]whendetectBarcodesis off, no codes are found, or the decode step fails or times out — it never throws and never affectsresult.detections.- Numeric 1D codes (UPC-A/UPC-E) decode to their expanded digit string (e.g.
"0012345678905"), so don’t expect an exact round-trip of a shorter printed value.code-39returns the encoded text as-is.
Pinning a model version
Models are versioned. By default a request runs whichever version is currently active, so results follow promotions automatically:
Versions are 1-based. Pin when you need reproducible output — a benchmark, a
regression suite, or a customer held to a specific model revision. Leave it
unset to get improvements as they’re promoted. The version also selects that
version’s confidence/IoU thresholds and detection-item tags, not just its
weights. A version that doesn’t exist throws ModelNotFoundError.
Multi-tenant / per-request credentials
The client can hold one credential for its lifetime, or none at all. If you’re
a multi-tenant proxy whose credentials arrive with each request and are
never stored server-side, build one shared client with no stored
credentials and pass apiKey / organizationId per detect() call — they
ride each request, and nothing is kept between calls:
Over NATS (WebSocket)
For lower latency and a persistent connection, use the NATS transport. Pass
transport: "nats" and the NATS endpoint; the same API key works. The
organization is resolved from the key when you don’t pass it, and the NATS
server confines the connection to that organization.
- One tenant per client. NATS binds the key to the connection, so per-call
apiKey/organizationIdoverrides throw aTypeError. Use one client per tenant, or HTTP for the multi-tenant proxy pattern. - Same errors and retries as HTTP. A rejected key is an
AuthenticationError; timeouts and 5xx replies are retried like HTTP. - Call
close()when you’re done, or the open WebSocket keeps Node running.
Validating credentials (login gate)
To accept an API key + org before a model is chosen (e.g. a web login
step), use validate() — it needs no model id and no image:
validate() resolves true/false — it calls GET /models, so an accepted
key gets a 200 (a real positive signal) and a rejected key (401/403) →
false. A missing route throws EndpointNotAvailableError rather than
masquerading as valid, so you never get a false “looks valid” from a route that
simply isn’t there — inspect statusCode on the thrown DetectionError if you
need to tell those apart. Both validate() and getDefaultModel() are HTTP
only and authenticate through the API gateway.
Listing models (model picker)
To let a user pick a model instead of pasting a UUID, listModels() returns
the org’s selectable models — no model id or image needed:
It returns top-level models (specialist sub-models are excluded), each with
id, name, and status. HTTP only, authenticated through the gateway;
organizationId / apiKey override the client’s defaults.
Putting it together — a typical flow
The methods above compose into one flow: validate the key at login → list models to pick one → detect on each capture. How you hold the client depends on your architecture.
Single-tenant app (one org, credentials known at startup) — one client for its lifetime:
Multi-tenant proxy (credentials arrive per request, nothing stored) — one shared client, credentials passed per call:
Distinguish a real credential failure from a gateway routing error at the
login gate — see Error Handling for the 401 vs 403
pattern.
Annotating detections (draw boxes)
Turn a /detect response + the source image into draw-ready boxes. Pass the
raw payload (detectRaw()), unchanged:
result.boxes (an AnnotationBox[]) is the primary product — pixel-space,
label-resolved specs you can render in any toolkit (sharp, canvas, SVG). Options:
{ label?: "name" | "id", color?: string, render?: boolean, names?: Record<string, string> }
(label defaults to "name", color defaults to #00FF00).
You can also build the same boxes directly from a payload + known image dimensions, without a client, via the free function:
result.toPng() renders those boxes onto the source image and resolves with
PNG bytes — but it needs a canvas backend (a browser OffscreenCanvas/<canvas>,
or createImageBitmap). In a plain Node process it rejects with a message
telling you to render .boxes yourself:
Handle the no-detections case — an empty payload throws NoDetectionsError
instead of returning a blank image:
Name labels are pulled by id from the model’s detection-items (the metadata
endpoint, a sibling of your inference endpoint), cached per model and falling
back to the id. The SDK derives that URL from an https://…/inference endpoint;
if yours differs, pass metadataEndpoint when constructing the client — or
pass a names: { id: name } map to annotateDetections to skip the network call.
Configuration Options
ClientOptions passed to new DetectionClient({ ... }):
The compression truth: the base SDK ships no image library (zero
runtime dependencies), so compression is a passthrough by default — bytes
are sent unchanged even with compression: true. This matches the Python
SDK’s behavior on a base install without Pillow. Supply compress (an
ImageCompressor) to actually resize/re-encode; the jpegQuality /
maxDimension / highCompression knobs are then passed to it as
CompressOptions.
Error Handling
Every failure surfaces as a typed subclass of DetectionError, each carrying
statusCode / responseBody (both undefined for client-side errors, i.e.
ones that never reached an HTTP response):
Transient failures (network errors, 5xx) are retried with exponential backoff
before surfacing as ConnectionError or ServerError.
A missing route is not a rejected key. An API Gateway returns 403 MISSING_AUTHENTICATION_TOKEN for an unmatched route even when the key is
valid. The SDK recognizes that signature and throws EndpointNotAvailableError
(a distinct type — not AuthenticationError), so a route that simply isn’t
deployed never masquerades as “bad credentials”:
validate() resolves true/false for a genuine accept/reject, and throws
EndpointNotAvailableError when the route isn’t deployed — it never answers a
confident false for a route that isn’t there.
Development
Run the example against a live env:
Live sandbox test — proves a real detect() succeeds and a bad key is
actually rejected, end to end against the sandbox environment:
(Requires an AWS SSO refresh first; re-run it if you hit an auth error.)
More detail lives alongside this package: docs/USER_GUIDE.md
(task-oriented walkthrough), MIGRATION.md (moving off direct
HTTP), BUILD.md (contributors), and tests/README.md
(what each test file covers).
License
MIT