TypeScript SDK
Install, configure and use the artefaktum npm package: push, pull, search and resolve artifacts
Install
npm install artefaktum
Zero runtime dependencies, Node 20+, ESM and CommonJS builds. Types are generated from the API’s own OpenAPI document, so a server-side rename is a compile error, not a runtime surprise.
import { Artefaktum } from "artefaktum";
// CommonJS: const { Artefaktum } = require("artefaktum");
Configure
Create an API key in the console.
| Option | Environment | Default |
|---|---|---|
apiKey |
ARTEFAKTUM_API_KEY |
required; throws MissingApiKeyError if missing |
baseUrl |
ARTEFAKTUM_BASE_URL |
https://api.artefaktum.dev |
project |
ARTEFAKTUM_PROJECT |
"default" |
timeoutMs |
none | 30000 per API call; storage transfers have no timeout |
fetch |
none | globalThis.fetch |
project accepts a UUID or slug; a slug is resolved once and cached. Every project-scoped method also takes project to override it for one call.
Methods are camelCase; fields are snake_case, spelled as the REST API, MCP tools and Python SDK spell them (external_key, latest_version.size_bytes, stale_upstream). Timestamps accept an ISO 8601 string or a Date; durations name their unit (timeoutMs, max_age_seconds).
Example
import { Artefaktum } from "artefaktum";
const client = new Artefaktum(); // reads ARTEFAKTUM_API_KEY; project "default"
const artifact = await client.artifacts.push("report.pdf", {
title: "Q3 churn analysis",
tags: ["churn", "q3"],
external_key: "reports/q3-churn",
});
const page = await client.artifacts.search("q3 churn");
for (const hit of page.items) console.log(hit.score, hit.artifact.title);
await client.artifacts.pull(artifact.id, "out/"); // → out/report.pdf, sha256-verified
Uploading
push(source, options) hashes the source, reserves an upload, PUTs the bytes directly to object storage (your API key never reaches that host), completes it, and by default waits until the artifact is ready.
await client.artifacts.push("data/export.parquet", { title: "Export" }); // path (Node): streamed
await client.artifacts.push(bytes, { title: "Export", filename: "export.json" }); // Uint8Array | ArrayBuffer | Blob
source is a file path (Node only) or bytes. Bytes need filename unless the Blob is a named File; content_type defaults to the Blob’s type, else it’s guessed from the name.
| Option | Meaning |
|---|---|
title |
Required. What the artifact is. |
description, tags, metadata |
Description, tag list, arbitrary JSON metadata. |
external_key |
Your own unique key for this artifact within the project. |
expires_at |
ISO string or Date; the artifact is deleted after this time. |
run, infer_lineage |
Link to a run id; infer_lineage (default true) links what the run read. |
wait, timeout_ms, signal |
Wait for ready (default true, 30 000 ms); signal cancels the transfer. |
Field sizes are limited; see Limits.
Storage transfers have no timeout of their own, since a total timeout would kill a large upload. Pass signal for a bound, e.g. AbortSignal.timeout(10 * 60_000).
A failed PUT throws StorageError and leaves the artifact pending_upload, never ready and invisible to search, so it is safe to retry. With wait: true, a processing failure throws ProcessingFailedError or ProcessingTimeoutError, both carrying .artifact.
createVersion(artifact_id, source, options) adds a version through the same flow (run, infer_lineage, content_type, filename, plus the wait options).
Compute once, reuse everywhere
resolve and fulfil cache an expensive computation under an external_key, so a later caller gets the stored result instead of recomputing it.
const r = await client.artifacts.resolve("openweather/vilnius/2026-09-21", {
filename: "weather.json", content_type: "application/json", size_bytes: body.byteLength,
title: "Vilnius weather", max_age_seconds: 3600,
});
if (r.status === "hit" && r.artifact) use(r.artifact);
else if (r.status === "create") await client.artifacts.fulfil(r, body);
else await sleep((r.retry_after_seconds ?? 2) * 1000); // "pending": someone else is producing it
resolve(external_key, options) requires filename, content_type, size_bytes, title, and takes description, tags, metadata, max_age_seconds, run. status is "hit" (fresh artifact in .artifact), "create" (call fulfil with this resolution and the bytes), or "pending" (someone else is producing it; back off using .retry_after_seconds). fulfil throws TypeError unless status is "create".
Downloading
const path = await client.artifacts.pull(id, "out/"); // Node: streams to disk, verifies sha256
const { data, filename } = await client.artifacts.pullBytes(id); // any runtime: verified bytes in memory
pull is Node-only; dest is a directory when it exists as one or ends with a separator (then created), otherwise the file to write. pullBytes works on any runtime, returning { data, filename, content_type, version_id, sha256 }. Both take { verify?, signal? }; verify defaults to true and a mismatch throws IntegrityError, leaving no file behind.
downloadUrl(artifact_id, { version_id?, run? }) returns the signed URL directly.
Finding
await client.artifacts.search("emission factors", { mode: "hybrid", tags_all: ["ghg"] });
await client.artifacts.getByExternalKey("reports/q3-churn");
for await (const a of client.artifacts.iterAll({ tag: "churn" })) console.log(a.title);
| Method | Purpose |
|---|---|
search(query, options) |
Ranked results. mode: "hybrid" (default), "semantic", "text", "exact". Also content_types, tags_all, status, external_key, created_after/before, exclude_superseded, limit (20), cursor, project. Returns { items: [{ score, artifact }], next_cursor }. |
get(artifact_id) |
Fetch one artifact by id. |
getByExternalKey(key, { project? }) |
Fetch by external key. |
list(options) |
One page: status, content_type, tag, external_key, created_before/after, expires_before, limit (50), cursor. |
iterAll(options) |
Async generator over every match, following next_cursor. |
Search hides superseded artifacts by default (exclude_superseded); each result reports whether it is superseded or stale_upstream.
Versions and relations
await client.artifacts.versions(artifact_id);
await client.artifacts.relations(artifact_id);
await client.artifacts.addRelation(artifact_id, to_artifact_id, "derived_from");
versions lists an artifact’s versions; relations lists its relations to other artifacts. addRelation(artifact_id, to_artifact_id, relation_type, { to_version_id?, metadata? }) requires relation_type to be one of derived_from, supersedes, attachment_of, generated_by, related_to; anything else throws TypeError first.
update(artifact_id, options) patches title, description, tags, metadata, expires_at; only fields passed are sent, and clear_expires_at: true removes the expiry. delete(artifact_id) is accepted with 202; bytes are removed in a background job.
Other namespaces: client.projects.list(); client.runs.create/.seal/.artifacts for grouping by pipeline run; client.keys, client.usage (admin scope); client.whoami(), client.quota().
Errors
Every failure from the API, storage, or the SDK’s own helpers is an ArtefaktumError with code, message, status, request_id (quote it in support mail). MissingApiKeyError extends plain Error. Caller mistakes the compiler can’t catch in JavaScript throw TypeError: fulfil on a non-create resolution, an unknown relation_type, nameless bytes without filename, an invalid baseUrl.
| Class | code |
|---|---|
NotFoundError |
artifact_not_found, run_not_found, not_found, project_not_found |
UnauthorizedError / ForbiddenError |
unauthorized / insufficient_scope |
QuotaExceededError |
quota_exceeded |
RequestTooLargeError |
request_too_large |
ConflictError |
artifact_not_ready, external_key_conflict, idempotency_conflict, run_sealed |
ValidationError |
invalid_request |
UploadError |
upload_expired, object_verification_failed |
ServiceUnavailableError |
embedding_unavailable |
ConnectionError |
connection_error: API unreachable after retries |
StorageError |
storage_error: object storage refused or was unreachable |
ProcessingFailedError / ProcessingTimeoutError |
processing_failed / processing_timeout (both carry .artifact) |
IntegrityError |
integrity_error: downloaded bytes don’t match the recorded sha256 |
ArtefaktumError |
version_mismatch: the artifact gained a version mid-pull; retry |
ArtefaktumError |
invalid_filename, unsupported_runtime (a file path on a runtime without fs), http_error (non-JSON response) |
try {
await client.artifacts.get(id);
} catch (err) {
if (err instanceof ArtefaktumError && err.code === "artifact_not_found") { /* … */ }
else throw err;
}
Prefer err.code to instanceof in libraries: a program loading both the ESM and CommonJS build sees distinct classes.
Reads (every GET, and search) retry up to twice on 429/502/503/504 and network failures, honouring Retry-After up to a 10 s ceiling. Writes, storage transfers and spent quota are never retried.
Runtimes
Tested on Node 20 and 22. The core uses only fetch and Web Crypto, so it should also run on Bun, Deno and Cloudflare Workers. Pass bytes or a Blob to push and use pullBytes there, since file paths need Node’s fs. Browsers are not supported: an API key does not belong in one.
See also Quickstart and REST.
This page as Markdown: /docs/typescript.md