# Python SDK

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

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

`superartifacts` needs Python 3.10 or newer. It is [server side only](https://pr-1769-stg.superartifacts.app/docs/sdks.md#server-side-only):
keep the key on a server.

**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 Python SDK to this project. Read https://superartifacts.app/docs/sdks/python.md first. Install superartifacts 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
pip install superartifacts
```

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

## First call

```python
import os

from superartifacts import SuperArtifacts

sa = SuperArtifacts(api_key=os.environ["SUPERARTIFACTS_API_KEY"])

print(sa.account.whoami()["handle"])

sa.artifacts.publish(
    slug="hello",
    prompt="A one-line hello page",
    html="<!doctype html><h1>Hello</h1>",
)

for artifact in sa.artifacts.list()["artifacts"]:
    print(artifact["slug"])

for row in sa.data.iter_artifact_rows(artifact="hello"):
    print(row)
```

`api_key` falls back to `SUPERARTIFACTS_API_KEY`, so `SuperArtifacts()` is enough.

Keyword arguments hold path, query and body fields together, in snake_case: `values_file=` goes out
as `valuesFile`. Values nested inside a dict keep the API's spelling. Answers are plain dicts, as
[API endpoints](https://pr-1769-stg.superartifacts.app/docs/reference/api-endpoints.md) describes them.

`AsyncSuperArtifacts` is the same surface, awaited.

```python
import asyncio

from superartifacts import AsyncSuperArtifacts


async def main():
    async with AsyncSuperArtifacts() as sa:
        print(await sa.account.whoami())
        async for row in sa.data.iter_artifact_rows(artifact="hello"):
            print(row)


asyncio.run(main())
```

## Configuration

```python
SuperArtifacts(
    api_key=None,  # or SUPERARTIFACTS_API_KEY
    base_url="https://superartifacts.app",
    timeout=60.0,  # seconds
    max_retries=2,
    http_client=None,  # your own httpx.Client; yours to close
)
```

Every method also takes `timeout=` for that call, and every `POST` takes `idempotency_key=`. Use
the client as a context manager, or call `close()`.

## Publish a page

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

```python
page = sa.artifacts.publish(
    slug="launch-notes",
    title="Launch notes",
    prompt="A page of launch notes.",
    html="<h1>Launch notes</h1>",
)
print(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.

```python
from pathlib import Path

sa.artifacts.publish(
    slug="rsvp",
    prompt="An RSVP form.",
    html=Path("rsvp.html").read_text(),
    policy={
        "policyVersion": 1,
        "writes": "upsert",
        "perUser": "single",
        "read": "own",
        "retentionDays": None,
        "recordCap": {"perVisitor": 1, "perArtifact": 500},
        "fields": [
            {"name": "attending", "type": "boolean", "required": False, "pii": "none", "description": "Yes or no."},
        ],
    },
)

for row in sa.data.iter_artifact_rows(artifact="rsvp"):
    print(row)
```

## Share an artifact

```python
sa.shares.add_to_artifact(
    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

Everything raised is a `SuperArtifactsError`, with `.status`, `.code`, `.message`, `.retry_after`
and the full error object in `.details`. [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`.

| Status | Exception |
| --- | --- |
| 400 | `InvalidArgumentsError` |
| 401 | `AuthenticationError` (also raised when there is no key) |
| 402 | `PlanLimitError` |
| 403 | `PermissionError`, or `ScopeRequiredError` when a read key tried a write |
| 404 | `NotFoundError` |
| 409 | `ConflictError` |
| 413 | `PayloadTooLargeError` |
| 429 | `RateLimitError` |
| 5xx | `InternalError` |
| none | `APIConnectionError`, `APITimeoutError` |

```python
from superartifacts import NotFoundError

try:
    sa.artifacts.get(artifact="missing")
except NotFoundError as error:
    print(error.code, error.message)
```

`superartifacts.PermissionError` is not the builtin of that name: import it by name when you catch
it.

## Retries and idempotency

Up to `max_retries` more tries, two by default:

- A `429` is retried after its `Retry-After`. Past 60 seconds it is not waited out: the
  `RateLimitError` comes straight back with `retry_after` set.
- `502`, `503`, `504` and network errors are retried with exponential backoff and jitter, for
  `GET`, `DELETE` and `POST`.
- `PATCH` and `PUT` are retried on `429` only. `500` and every other `4xx` are never retried.

Every `POST` sends an `Idempotency-Key`, one per call and the same across its retries, so a retried
publish happens once. Pass `idempotency_key=` to choose your own, which also makes a retry of your
own code safe.

## Pagination

`iter_*` methods walk every page and yield one item at a time, taking the same arguments as the
list they wrap.

```python
for row in sa.data.iter_rows(dataset="signups"):
    print(row)
for row in sa.data.iter_artifact_rows(artifact="hello"):
    print(row)
for entry in sa.entries.iter(collection="posts"):
    print(entry)
for mention in sa.mentions.iter():
    print(mention)
```

## Uploads

A JSON body stops at 1 MiB. `uploads.upload` stages bigger files and sends the bytes straight to
their upload URLs. It takes a mapping of path in the artifact to bytes or a local file, within 5 MiB
a file and 10 MiB a set.

```python
staged = sa.uploads.upload({"images/hero.png": "./hero.png", "notes.txt": b"hello"})

sa.artifacts.publish(
    slug="hello",
    prompt="A page with a picture",
    html='<!doctype html><img src="images/hero.png">',
    uploads=staged["uploadId"],
)
```

## 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.read_file` | 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.serve_version` | 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.list_artifact_rows` | Rows visitors submitted to one artifact, newest first |
| `sa.data.iter_artifact_rows` | Every row of one artifact, page by page |
| `sa.data.write_artifact_rows` | Write rows into an artifact you may edit |
| `sa.data.list_rows` | Rows submitted to a dataset or a gallery label |
| `sa.data.iter_rows` | 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.define_collection` | 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.get_history` | A template's changelog |
| `sa.templates.get_feedback` | 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.add_artifacts` | Add artifacts to a project |
| `sa.projects.remove_artifact` | Remove one artifact (or item) from a project |
| `sa.shares.add_to_artifact` | Give named people access to an artifact |
| `sa.shares.add_to_project` | Give named people access to a project |
| `sa.shares.add_to_workspace` | 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.get_screenshot` | 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 |
