TypeScript SDK
Install @superartifacts/sdk, publish a page, read back what it collected, share it, and handle errors, retries, paging and uploads.
guide~4 minVerified v0.178.0
@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: 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.
Install
npm install @superartifacts/sdk
Create a key in Settings → API keys and put it in
SUPERARTIFACTS_API_KEY.
First call
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.
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
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.
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 describes.
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
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 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 |
RateLimitError | 429 |
InternalError | 5xx |
ConnectionError | no answer arrived (status 0) |
TimeoutError | the attempt ran out of time (status 0) |
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
429is retried after itsRetry-After. Past 60 seconds it is not waited out: theRateLimitErrorcomes straight back withretryAfterset. 502,503,504, timeouts and network errors are retried with backoff onGET,DELETEand anyPOSTcarrying anIdempotency-Key.- Every
POSTcarries 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
4xxis retried, and neither is aPATCHorPUT.
Pagination
Operations that page have an iterator that fetches pages as you read.
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.
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 |