# TypeScript SDK

> Install @superartifacts/sdk, publish a page, read back what it collected, share it, and handle errors, retries, paging and uploads.

Kind: guide · ~4 min · Verified v0.178.0 · Next: [Python SDK](https://pr-1769-stg.superartifacts.app/docs/sdks/python.md)

`@superartifacts/sdk` has no runtime dependencies. Node 20.3+. It uses only the standard `fetch`, so other runtimes that provide it may work; only Node is tested.

It is [server side only](https://pr-1769-stg.superartifacts.app/docs/sdks.md#server-side-only): the client throws if you construct it in a
browser. `dangerouslyAllowBrowser: true` turns that check off for use behind your own proxy, and
ships the key to every visitor.

**Add the SDK with your coding agent.** Your agent installs it, wires up the key and makes a first call.

```prompt
Add the Super Artifacts TypeScript SDK to this project. Read https://superartifacts.app/docs/sdks/typescript.md first. Install @superartifacts/sdk with the package manager this project uses, and call it only from server-side code, because the API refuses browsers. Read the key from the SUPERARTIFACTS_API_KEY environment variable, add that name to the example env file, and never write a key into code. If the variable is not set, ask me to create a key in Settings, API keys, and to set it myself. Then make one call to sa.account.whoami() and tell me which workspace answered.
```

## Install

```bash
npm install @superartifacts/sdk
```

Create a key in [Settings → API keys](https://superartifacts.app/settings/api-keys) and put it in
`SUPERARTIFACTS_API_KEY`.

## First call

```ts
import { SuperArtifacts } from '@superartifacts/sdk'

const sa = new SuperArtifacts()

const me = await sa.account.whoami()
console.log(me)

const published = await sa.artifacts.publish({
  slug: 'hello',
  title: 'Hello',
  prompt: 'A page that says hello.',
  html: '<h1>Hello</h1>',
})
console.log(published)

const { artifacts } = await sa.artifacts.list()
console.log(artifacts)

for await (const row of sa.data.iterArtifactRows({ artifact: 'hello' })) {
  console.log(row)
}
```

Every method takes one object holding the operation's path, query and body fields together, and an
optional second argument for that request alone.

```ts
await sa.artifacts.update({ artifact: 'hello', visibility: 'private' }, { timeout: 30_000 })
```

Responses are untyped JSON until the API describes them: every result is an open object, so
destructure and index it without a cast.

## Configuration

```ts
new SuperArtifacts({
  apiKey: process.env.SUPERARTIFACTS_API_KEY, // the default
  baseUrl: 'https://superartifacts.app',      // the default
  timeout: 60_000,                            // milliseconds per attempt
  maxRetries: 2,
  fetch: globalThis.fetch,
})
```

Per request: `{ idempotencyKey, timeout, signal, headers }`.

## Publish a page

A page is private when it first publishes. Publishing the same slug again adds a new version at the
same address.

```ts
const page = await sa.artifacts.publish({
  slug: 'launch-notes',
  title: 'Launch notes',
  prompt: 'A page of launch notes.',
  html: '<h1>Launch notes</h1>',
})
console.log(page.url)
```

## Collect data and read it back

Without a `policy`, a form stores nothing. This one is an answer sheet: one row per person. The
form in `rsvp.html` saves to it, as [Collect responses](https://pr-1769-stg.superartifacts.app/docs/collect-data.md) describes.

```ts
import { readFile } from 'node:fs/promises'

await sa.artifacts.publish({
  slug: 'rsvp',
  prompt: 'An RSVP form.',
  html: await readFile('rsvp.html', 'utf8'),
  policy: {
    policyVersion: 1,
    writes: 'upsert',
    perUser: 'single',
    read: 'own',
    retentionDays: null,
    recordCap: { perVisitor: 1, perArtifact: 500 },
    fields: [
      { name: 'attending', type: 'boolean', required: false, pii: 'none', description: 'Yes or no.' },
    ],
  },
})

for await (const row of sa.data.iterArtifactRows({ artifact: 'rsvp' })) {
  console.log(row)
}
```

## Share an artifact

```ts
await sa.shares.addToArtifact({
  artifact: 'launch-notes',
  emails: ['dana@example.com'],
  role: 'commenter',
})
```

Each person gets an email. The role is `viewer` unless you say otherwise, and sharing endpoints
allow 20 calls an hour per key.

## Errors

Every failure is a `SuperArtifactsError` with `status`, `code`, `message` and, when the API asked
you to wait, `retryAfter` in seconds. [Error codes](https://pr-1769-stg.superartifacts.app/docs/reference/errors.md) lists every `code`. A
client-side failure has `status` 0 and its own `code`: a missing key is an `AuthenticationError` with
`missing_api_key`.

| Class | When |
| --- | --- |
| `InvalidArgumentsError` | 400 |
| `AuthenticationError` | 401, or no key to send |
| `PlanLimitError` | 402 |
| `PermissionError` | 403 `forbidden` |
| `ScopeRequiredError` | 403 `scope_required`: a read key called a write |
| `NotFoundError` | 404 |
| `ConflictError` | 409 |
| `PayloadTooLargeError` | 413: over 1 MiB, see [Uploads](#uploads) |
| `RateLimitError` | 429 |
| `InternalError` | 5xx |
| `ConnectionError` | no answer arrived (`status` 0) |
| `TimeoutError` | the attempt ran out of time (`status` 0) |

```ts
import { NotFoundError } from '@superartifacts/sdk'

try {
  await sa.artifacts.get({ artifact: 'nope' })
} catch (error) {
  if (error instanceof NotFoundError) console.log(error.code)
  else throw error
}
```

## Retries and idempotency

- A `429` is retried after its `Retry-After`. Past 60 seconds it is not waited out: the
  `RateLimitError` comes straight back with `retryAfter` set.
- `502`, `503`, `504`, timeouts and network errors are retried with backoff on `GET`, `DELETE` and
  any `POST` carrying an `Idempotency-Key`.
- Every `POST` carries one: a fresh UUID per call, reused across that call's retries, so a retried
  publish happens once. Pass `{ idempotencyKey }` to make a call safe to repeat across restarts, or
  `{ idempotencyKey: false }` to send none, which turns these retries off.
- No other `4xx` is retried, and neither is a `PATCH` or `PUT`.

## Pagination

Operations that page have an iterator that fetches pages as you read.

```ts
for await (const row of sa.data.iterRows({ dataset: 'signups' })) console.log(row)
for await (const row of sa.data.iterArtifactRows({ artifact: 'hello' })) console.log(row)
for await (const entry of sa.entries.iter({ collection: 'posts' })) console.log(entry)
for await (const mention of sa.mentions.iter()) console.log(mention)
```

`sa.comments.list` returns one page.

## Uploads

A request body is capped at 1 MiB. Bigger files are staged, then published by id.

```ts
import { readFile } from 'node:fs/promises'

const { uploadId } = await sa.uploads.upload([
  { path: 'images/hero.png', data: await readFile('hero.png') },
])

await sa.artifacts.publish({
  slug: 'hello',
  prompt: 'A page with a hero image.',
  html: '<img src="images/hero.png">',
  uploads: uploadId,
})
```

One file may be 5 MiB and one upload 10 MiB. A staged set expires after 30 minutes.

## Every method

| Method | Does |
| --- | --- |
| `sa.account.whoami` | Who this key publishes as |
| `sa.account.help` | How the operations fit together |
| `sa.artifacts.list` | List your artifacts, newest first |
| `sa.artifacts.publish` | Publish an artifact, or a new version of one |
| `sa.artifacts.get` | One artifact: its versions, published version and declaration |
| `sa.artifacts.update` | Change visibility, gallery label, live phase or submission email |
| `sa.artifacts.delete` | Delete an artifact with every version, permanently |
| `sa.artifacts.readFile` | Read one file of a version as text (default: the published index.html) |
| `sa.artifacts.open` | A ten-minute viewer URL for an artifact, private included |
| `sa.artifacts.serveVersion` | Serve an existing version again |
| `sa.live.start` | Turn an artifact's live room on |
| `sa.live.stop` | Turn an artifact's live room off, disconnecting every visitor |
| `sa.uploads.stage` | Stage big files for a publish; PUT each to its URL |
| `sa.uploads.upload` | Stage files and send their bytes |
| `sa.data.listArtifactRows` | Rows visitors submitted to one artifact, newest first |
| `sa.data.iterArtifactRows` | Every row of one artifact, page by page |
| `sa.data.writeArtifactRows` | Write rows into an artifact you may edit |
| `sa.data.listRows` | Rows submitted to a dataset or a gallery label |
| `sa.data.iterRows` | Every row of a dataset or label, page by page |
| `sa.data.query` | Aggregate what was collected: numbers, never rows |
| `sa.schema.get` | The data catalog: datasets and content collections, or a comparison |
| `sa.schema.defineCollection` | Create or replace a content collection |
| `sa.entries.list` | One page of a content collection's entries |
| `sa.entries.iter` | Every entry, page by page |
| `sa.entries.create` | Create one entry, or a batch |
| `sa.secrets.use` | Call an approved API with a stored secret you cannot read (workspace keys) |
| `sa.templates.list` | Find, rank or list templates |
| `sa.templates.save` | Create or revise a template draft |
| `sa.templates.get` | One template in full, with its install prompt |
| `sa.templates.getHistory` | A template's changelog |
| `sa.templates.getFeedback` | Comments on a template's artifacts, as themes |
| `sa.templates.act` | Install, uninstall, rate, report, roll back or request verification |
| `sa.templates.release` | Release one version of your template |
| `sa.projects.list` | Your projects |
| `sa.projects.create` | Create a project, optionally seeded with artifacts |
| `sa.projects.get` | One project and its items |
| `sa.projects.update` | Rename, set visibility, reorder, move, fold groups or set a rule |
| `sa.projects.delete` | Delete a project; its artifacts stay |
| `sa.projects.addArtifacts` | Add artifacts to a project |
| `sa.projects.removeArtifact` | Remove one artifact (or item) from a project |
| `sa.shares.addToArtifact` | Give named people access to an artifact |
| `sa.shares.addToProject` | Give named people access to a project |
| `sa.shares.addToWorkspace` | Invite editors to a workspace (returns accept links) |
| `sa.comments.list` | Comment threads on an artifact, newest first |
| `sa.comments.respond` | Reply to and/or resolve threads, one outcome each |
| `sa.comments.getScreenshot` | One message's screenshot, base64 in `image` |
| `sa.mentions.list` | Where people @mentioned you; listing marks them seen |
| `sa.mentions.iter` | Every mention, page by page |
| `sa.signatures.list` | An artifact's signature requests, newest first |
| `sa.signatures.request` | Ask named people to sign one version of an artifact |
| `sa.signatures.get` | One signature request and its signers |
| `sa.signatures.remind` | Email a signer their link again |
| `sa.signatures.withdraw` | Close an open signature request |
