Docs

Client libraries

JavaScript

@auray/client: the API from JavaScript and TypeScript, with zero dependencies.

npm install @auray/clientv0.3.0

Zero 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:

OptionWhat it is
credentialsThe key, or a function returning it. Falls back to AURAY_API_KEY in the environment.
fetchYour own fetch, for a proxy or a test.
origins{ platform, queue, run, openai } overrides (openai since 0.2.0). You will not need these.
retriesHow 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. A 504 run_timed_out is followed to the same result by the job it names, queue_request_id — never by its request_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; timeoutMs is the whole call's deadline. Left out, a follow waits as long as the server says the job may take — give_up_after_seconds on its status reads — plus 6 minutes for the sweep that ends an overdue job, and 20 minutes until the server has said. Running out throws subscribe_timed_out with the job in error.body — charged, queue_request_id, status_url, response_url, cancel_url — and followTarget(error.body) reads it back. An error the follow throws keeps the server's own 504 run_timed_out as its cause (since 0.2.2), an AurayError whose charged and sentence are the server's: of a run still waiting in the queue it says charged: 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. Without timeoutMs it waits what the submit's give_up_after_seconds says — a video render may take hours, and a fixed deadline gave up on it. (maxQueueLength, requestTimeoutSeconds and the server-sized wait: since 0.2.0.) priority and maxQueueLength shape a queue, and only a model that queues has one — lifecycle.queue is waits on GET /v1/models; a model that refuses when full, every catalogue model today, answers them 422 queue_not_offered before anything is charged. requestTimeoutSeconds is 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, one QueueStatus per server-sent event, until COMPLETED. The server closes each stream after a few minutes; the iterator reopens it after the stream's own retry: pause (since 0.2.1 — 0.2.0 ended there, with the job still running). queue.result of a request that has not completed throws request_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.

Start typing to search every page of the documentation.

Up arrow Down arrow to moveEnter to openEscape to close