Client libraries
JavaScript
@auray/client: the API from JavaScript and TypeScript, with zero dependencies.
npm install @auray/clientv0.3.0Zero runtime dependencies. Node 20 or newer, and any runtime that has fetch — Deno, Bun,
Cloudflare Workers, a browser bundle that is not handed a secret key.
From CommonJS — an Express or NestJS project as generated, TypeScript with module: commonjs — require it
(since 0.2.2; 0.2.0 and 0.2.1 could only be imported, and require failed with ERR_PACKAGE_PATH_NOT_EXPORTED):
const { createClient, AurayError } = require("@auray/client");The package carries an ES module build and a CommonJS one. A Node that can require an ES module (20.19 and 22.12
onward) is handed the ES module either way, so a process that loads the client by import and by require holds one
copy of it and one AurayError; an older Node's require gets the CommonJS build. TypeScript finds the types under
node10, node16, nodenext and bundler resolution; classic, which module: esnext falls back to when no
moduleResolution is set, reads no node_modules and finds no npm package, so name one of the four.
#Your first render
import { createClient } from "@auray/client";
const auray = createClient({ credentials: process.env.AURAY_API_KEY });
const result = await auray.subscribe("auray-ai/krea-2-turbo/text-to-image", {
input: { prompt: "a brass orrery on a walnut desk, morning light", aspect_ratio: "16:9", resolution: 1024 },
idempotencyKey: crypto.randomUUID(),
onQueueUpdate: (s) => console.log(s.status),
});
const assets = await auray.platform.jobs.assets(result.request_id);
console.log(assets.assets[0].url);subscribe submits to the queue host, follows the request through IN_QUEUE and IN_PROGRESS
at the pace the API's Retry-After sets, and fetches the result once it is COMPLETED. The
signed download URLs come from the platform host in a second call, because
signing asset links is metered as egress and the client does not sign what you
did not ask for.
#The client
createClient(options) takes:
| Option | What it is |
|---|---|
credentials | The key, or a function returning it. Falls back to AURAY_API_KEY in the environment. |
fetch | Your own fetch, for a proxy or a test. |
origins | { platform, queue, run, openai } overrides (openai since 0.2.0). You will not need these. |
retries | How many times a refusal is resent whose own retry is same_key or after_retry_after, honouring Retry-After (since 0.2.0; 0.1.x resent every 429 and 503). Nothing else is resent. |
#Running a model
run(address, { input, idempotencyKey, waitSeconds, timeoutMs })— the synchronous host, which holds the connection for the answer. A504 run_timed_outis followed to the same result by the job it names,queue_request_id— never by itsrequest_id, which on an error is the trace of the call — and is never resubmitted.waitSeconds(1 to 300) is how long the host may hold the connection before it answers with the job;timeoutMsis the whole call's deadline. Left out, a follow waits as long as the server says the job may take —give_up_after_secondson its status reads — plus 6 minutes for the sweep that ends an overdue job, and 20 minutes until the server has said. Running out throwssubscribe_timed_outwith the job inerror.body—charged,queue_request_id,status_url,response_url,cancel_url— andfollowTarget(error.body)reads it back. An error the follow throws keeps the server's own504 run_timed_outas itscause(since 0.2.2), anAurayErrorwhosechargedand sentence are the server's: of a run still waiting in the queue it sayscharged: false, where the follow's error speaks of a charge.subscribe(address, { input, idempotencyKey, webhookUrl, waitSeconds, priority, maxQueueLength, requestTimeoutSeconds, logs, onQueueUpdate, pollIntervalMs, timeoutMs })— submit, follow, fetch the result. WithouttimeoutMsit waits what the submit'sgive_up_after_secondssays — a video render may take hours, and a fixed deadline gave up on it. (maxQueueLength,requestTimeoutSecondsand the server-sized wait: since 0.2.0.)priorityandmaxQueueLengthshape a queue, and only a model that queues has one —lifecycle.queueiswaitsonGET /v1/models; a model that refuses when full, every catalogue model today, answers them422 queue_not_offeredbefore anything is charged.requestTimeoutSecondsis a time to start, and such a model accepts it: it starts or refuses at once.queue.submit(address, { input, idempotencyKey, webhookUrl, waitSeconds, priority, maxQueueLength, requestTimeoutSeconds }),queue.status(address, { requestId, logs }),queue.result(address, { requestId }),queue.cancel(address, { requestId })— the request lifecycle, one call each.queue.streamStatus(address, { requestId, logs })— an async iterator over the status stream, oneQueueStatusper server-sent event, untilCOMPLETED. The server closes each stream after a few minutes; the iterator reopens it after the stream's ownretry:pause (since 0.2.1 — 0.2.0 ended there, with the job still running).queue.resultof a request that has not completed throwsrequest_in_progress.
#Files
storage.upload({ address, contentType, data, idempotencyKey }) (address and send_as since 0.2.0; on 0.1.1
name the product instead — threed or external, which a key minted with the kinds uses for the kinds of file
some address of its kinds takes, a mesh with 3d:write) is the three calls of
Uploads in one: ask for a slot for the address the file is for, PUT the bytes
straight to storage, ask for the signed read URL. It answers { path, bucket, url, expires_at, address, send_as }: send_as names the address's field that takes the file and whether it wants
the path or the signed url. expiresAt is still there, the same value under the spelling 0.1.x
answered, and product is still accepted in place of address.
#The account's side
platform.me(), platform.usage({ window }), platform.plans(), platform.catalogue(),
platform.status(), platform.changelog(), platform.jobs.{list, get, wait, cancel, assets},
platform.models.{list, document}, platform.webhooks.{list, create, verify, delete},
platform.chat.{turn, conversation, conversations, messages} — each one operation of the
platform reference, with the same names. Since 0.2.0: plans, catalogue, status, changelog,
usage's window, chat.conversations and chat.messages, the jobs.list filters and
webhooks.create's products. plans, catalogue, status and changelog need no
key and send one when the client has it; chat.conversations and chat.messages need chat:read, which
neither text:write nor the older chat:write stands in for. jobs.list takes the reference's filters (product,
model, settled, status), webhooks.create a products list, and jobs.wait waits what the
job's own give_up_after_seconds says unless given a timeoutMs. A test in the package calls every
operation the reference names, or says in words why a program has no use for it — the support
flows and MCP.
#The Community
platform.community (since 0.3.0) is the Community reference: published works, public
profiles, likes, follows, comments, collections and reports. The reads need no key — feed, search,
publications.{get, comments, remixes}, profiles.{get, publications, followers, following, collections, available}
and collections.get for a public collection — and a key that holds community:write reads as its account, its own
likes and follows filled in; any other key reads what a stranger reads. Everything that writes needs that scope, which a
developer key holds only when it was ticked as the key was made: it acts in public under the account's profile and
spends nothing.
const { data, next_cursor } = await auray.platform.community.feed({ sort: "trending", kind: "image", limit: 24 });
const work = await auray.platform.community.publications.publish({
jobId: "photo_8f14e45f-ceea-467f-a0e6-1d3d1c7a5b2e",
licence: true,
title: "Lighthouse at dusk",
});
console.log(work.url);publications.publish takes the id a submit answered and publishes that result: licence: true grants the licence
the Terms state for it (termsVersion names the version you accepted), asset picks one file of a job that made
several, and showPrompt and discoverable decide what strangers see. A video, a model or a song is published with a
poster you supply — { contentType, data }, PNG, JPEG or WebP, the bytes or their base64 — and a song may carry its
waveform. A work shows at once on your profile and its own link, and reaches the public feeds once its review
approves it. publications.{update, unpublish, comment, like, unlike, view, source, mine, liked},
comments.delete, profiles.{claim, update, delete, follow, unfollow, setAvatar, clearAvatar, setBanner, clearBanner}, collections.{mine, create, update, delete, add, remove}, me and report are the rest, one
method per operation. In update, a field given as null is cleared and a field left out is left as it is. report
also works with no key, as an anonymous report. Every page carries next_cursor; pass it back as cursor for the
next one.
#Embeddings
embeddings.create({ model, input, idempotencyKey }) (since 0.2.0) posts a string or a batch to the
OpenAI-compatible route, POST /openai/v1/embeddings — the key needs embeddings:write — and answers OpenAI's
list of vectors with credits_charged beside it:
const { data } = await auray.embeddings.create({ model: "qwen3-embedding-8b", input: ["a brass trumpet", "a tuba"] });
console.log(data[0].embedding.length);Idempotency-Key is sent only when you name one. A batch is sent again after embeddings_warming's Retry-After,
under the key you named as well: a batch refused with its whole charge given back does not use its key up, so the
identical resend is a fresh attempt, charged only if it is served, up to 8 times
for one key. A key that cannot be tried again — past those attempts, or a call a promotion paid for — is raised
with retry: "new_key" and not resent: send the batch under a new key.
OpenAI-compatible says what the model takes and costs.
#Errors
Every refusal throws an AurayError:
import { AurayError } from "@auray/client";
try {
await auray.run("auray-ai/krea-2-turbo/text-to-image", { input: { prompt: "…" } });
} catch (error) {
if (error instanceof AurayError) {
console.log(error.status, error.code, error.errorType, error.message, error.docs, error.requestId);
console.log("retry:", error.retry, "charged:", error.charged);
if (error.retryable) console.log("try again in", error.retryAfterSeconds, "seconds");
}
}code is the error field of the API's body and docs links its entry in
the catalogue. retry, charged and errorType are since 0.2.0. retry and charged are the body's own advice, read as the
catalogue explains them, and errorType its class. retryable is true when trying again can
succeed without paying twice — retry is same_key or after_retry_after, or new_key after a
refund — and never while charged is stands. It was true for every 429 and 5xx before 0.2.0,
a charged timeout included.
run is the one call the client never resends, because it submits. When every connection
auray.run could hold for it is in use, it throws 429 wait_capacity, refused before anything
was charged: the same call is safe to make again after retryAfterSeconds. subscribe queues
instead of holding a connection, so it never meets that refusal.
#Where it lives
packages/sdk-js in the repository. Its error class and its polling loop are byte-for-byte
mirrors of packages/client-core — the modules the desktop's API tab and this documentation
render through — held identical by a guard, so a fix lands in both or in neither.