Docs
Trigger runs from your own scripts, automations, or backend. Same quota and tier rules as the web app — your token acts as you.
Three calls take you from a fresh token to a finished run.
# 1. Discover prompts (pick one whose modality + variables fit)
curl -H "Authorization: Bearer arti_<token>" \
https://artinstring.com/api/v1/prompts | jq '.prompts[0]'
# 2. Dispatch a run with the prompt's id and the variables it needs
curl -X POST https://artinstring.com/api/v1/runs \
-H "Authorization: Bearer arti_<token>" \
-H "Content-Type: application/json" \
-d '{"promptId":"<uuid>","vars":{"subject":"a stoic owl"}}'
# → { "runId": "<uuid>", "status": "queued" }
# 3. Poll until completion (or use the SSE stream)
curl -H "Authorization: Bearer arti_<token>" \
https://artinstring.com/api/v1/runs/<runId>
# → { ..., "status": "completed", "output": { "url": "https://…" } }Need a long-lived stream instead of polling? Use GET /api/runs/:id/stream below.
Generate an API token at /me/api-tokens. Tokens look like arti_… and are shown once on creation — store them somewhere safe. Send each request with:
Authorization: Bearer arti_<your-token>Token introspection — confirms your token is valid and reports the user it belongs to. Handy as a smoke test from a CI pipeline.
curl \
-H "Authorization: Bearer arti_<your-token>" \
https://artinstring.com/api/v1/me200 → {
"id": "uuid",
"email": "[email protected]",
"tier": "pro",
"subscriptionStatus": "active",
"currentPeriodEnd": "2026-06-01T00:00:00.000Z",
"cancelAtPeriodEnd": false
}
401 → { "error": "unauthorized" }Current Replicate-routed quota usage by modality. Useful for SDK consumers that want a "you have N runs left" hint before dispatching. BYO-key runs are uncapped and not counted here.
curl \
-H "Authorization: Bearer arti_<your-token>" \
https://artinstring.com/api/v1/me/quotas{
"tier": "pro",
"note": "limits are Replicate-routed only; bring-your-own-key runs are uncapped",
"resetsAt": "2026-06-01T00:00:00.000Z",
"quotas": {
"image": { "used": 12, "limit": 200, "remaining": 188, "exhausted": false },
"video": { "used": 3, "limit": 30, "remaining": 27, "exhausted": false },
"text": { "used": 89, "limit": 1000, "remaining": 911, "exhausted": false }
}
}Enqueue a run for a published prompt. The prompt's required variables come from the page in the gallery — see variableSchema on the prompt detail page.
curl -X POST https://artinstring.com/api/v1/runs \
-H "Authorization: Bearer arti_<your-token>" \
-H "Content-Type: application/json" \
-d '{
"promptId": "9f1b...uuid",
"vars": { "subject": "a stoic owl at sunset" }
}'Optional fields:
useOwnKey (boolean) — for prompts routed to non-Replicate providers, set true to bill your own stored API keyResponses:
200 → { "runId": "uuid", "status": "queued" }
401 → { "error": "unauthorized" }
400 → { "error": "invalid_body", "fieldErrors": {...} }
402 → { "error": "tier_required", "tier": "pro" }
402 → { "error": "quota_exceeded", "modality": "image" }
429 → { "error": "rate_limited", "retryAfterSeconds": 30 }List the published curator-grouped collections. Each row carries a promptCount of the collection's published+public prompts so you can render a catalog overview without one round-trip per collection.
curl \
-H "Authorization: Bearer arti_<your-token>" \
https://artinstring.com/api/v1/collections{
"collections": [
{
"id": "uuid",
"slug": "cinematic-portraits",
"title": "Cinematic Portraits",
"description": "Studio-grade portraits with shallow depth of field and warm grading.",
"heroImageUrl": null,
"publishedAt": "2026-05-04T00:00:00Z",
"promptCount": 4
}
],
"count": 1, // entries returned in this response
"total": 6 // total published collections (mirrors X-Total-Count header)
}List the published, public prompts available to run. Capped at 200 — the catalog is small enough that this fits in one page today. Each entry includes variableSchema, which describes the run-form variables you'll supply when you POST to /api/v1/runs. Optional filters: ?modality=<image|video|text>, ?tier=<free|pro|studio>, ?provider=<replicate|fal|openai|anthropic>, ?limit=<1..200>, ?q=<substring> (case-insensitive ILIKE on title + slug + description; capped at 80 chars).
# All published prompts
curl -H "Authorization: Bearer arti_<your-token>" \
https://artinstring.com/api/v1/prompts
# Only Free-tier image prompts you can run without a BYO key
curl -H "Authorization: Bearer arti_<your-token>" \
"https://artinstring.com/api/v1/prompts?modality=image&tier=free&provider=replicate"
# Just the 5 newest prompts — useful for a "what's new" sidebar
curl -H "Authorization: Bearer arti_<your-token>" \
"https://artinstring.com/api/v1/prompts?limit=5"
# Substring search — useful for "does a prompt about X exist?"
curl -H "Authorization: Bearer arti_<your-token>" \
"https://artinstring.com/api/v1/prompts?q=portrait&modality=image"{
"prompts": [
{
"id": "uuid",
"slug": "stoic-owl",
"title": "Stoic Owl Portrait",
"description": "...",
"modality": "image",
"provider": "replicate",
"model": "black-forest-labs/flux-dev",
"tierRequired": "free",
"variableSchema": { ... },
"collectionTitle": "Cinematic Portraits",
"collectionSlug": "cinematic-portraits"
}
],
"count": 1, // entries returned in this response
"limit": 5, // limit applied to this query
"total": 87 // total prompts matching the same filters
}The total field reflects the same ?modality / ?tier / ?provider / ?q filters as the rows query — useful for rendering “showing X of Y” without a follow-up call. The same number is mirrored in the X-Total-Count response header for tooling that can't introspect the JSON body.
Fetch one prompt by slug or UUID — same value works in either column. Useful when your script already knows which prompt it wants and doesn't need to scan the catalog. Returns 404 if the prompt isn't published, or if it's a private remix authored by someone other than you.
curl \
-H "Authorization: Bearer arti_<your-token>" \
https://artinstring.com/api/v1/prompts/stoic-owl200 → {
"id": "uuid",
"slug": "stoic-owl",
"title": "Stoic Owl Portrait",
"description": "...",
"modality": "image",
"provider": "replicate",
"model": "black-forest-labs/flux-dev",
"tierRequired": "free",
"variableSchema": { ... },
"collectionTitle": "Cinematic Portraits",
"collectionSlug": "cinematic-portraits",
"publishedAt": "2026-05-01T00:00:00Z"
}
404 → { "error": "prompt_not_found" }One random curator-published prompt as JSON — the same shape as :slug. Useful for "surprise me" or "shuffle" features in your own UI. The page-level /random returns a 307 redirect, which is the right shape for a browser button but useless for a script. Returns 404 when the catalog is empty.
curl \
-H "Authorization: Bearer arti_<your-token>" \
https://artinstring.com/api/v1/prompts/random200 → { "slug": "stoic-owl", "modality": "image", ... }
404 → { "error": "prompt_not_found" }List your recent runs, newest-first. Returns up to 50 per page; pass ?cursor=<ISO timestamp> to fetch the next page using the nextCursor from the previous response. Optional ?status=<queued|running|completed|failed|cancelled> filters server-side — pairs with cursor pagination. ?since=<ISO timestamp> returns only runs created at or after the given time — useful for polling-since-last-poll integrations.
curl \
-H "Authorization: Bearer arti_<your-token>" \
"https://artinstring.com/api/v1/runs?status=completed"{
"runs": [
{
"id": "uuid",
"status": "completed",
"createdAt": "2026-05-08T01:23:45Z",
"completedAt": "2026-05-08T01:24:11Z",
"promptId": "uuid",
"promptSlug": "stoic-owl",
"promptTitle": "Stoic Owl Portrait"
}
],
"count": 1, // entries returned in this response
"total": 87, // total runs matching status + since filters
"nextCursor": "2026-05-07T22:11:08Z" | null
}The total field reflects the same ?status + ?since filters as the rows query but ignores ?cursor — paging through returns a stable total at every step. The same value is mirrored in the X-Total-Count response header.
One-shot fetch of a run's status. Useful for polling or callers that don't want a long-lived stream. Returns the same fields as the SSE stream.
curl \
-H "Authorization: Bearer arti_<your-token>" \
https://artinstring.com/api/v1/runs/<runId>Response shape:
{
"id": "uuid",
"status": "queued|running|completed|failed",
"error": null,
"createdAt": "...",
"completedAt": "...",
"output": { "url": "...", "text": "...", "mime": "..." } | null
}Server-Sent Events stream of a run's lifecycle. data: frames carry JSON of shape { status, output? }. The stream closes when the run completes or fails. Useful for watching long-running video / image generations without polling.
curl -N \
-H "Authorization: Bearer arti_<your-token>" \
https://artinstring.com/api/runs/<runId>/streamEvery error response is JSON of shape { error: string, ... }. Use the error code (not the HTTP status) for branching — codes are stable across versions.
| HTTP | error code | When |
|---|---|---|
| 401 | unauthorized | Missing or unrecognized Bearer token. |
| 400 | invalid_body | POST body failed Zod validation; fieldErrors describes the gaps. |
| 400 | invalid_cursor / invalid_since | Pagination/filter timestamp didn't parse as a Date. |
| 400 | invalid_status / invalid_modality / invalid_tier | Filter param outside the documented enum; allowed lists valid values. |
| 400 | missing_variables | POST /runs body missed required prompt vars; variables lists them. |
| 400 | missing_provider_key | BYO-key prompt routed to your provider but no key in /me/settings. |
| 402 | tier_required | Prompt requires a higher tier than your current one. |
| 404 | prompt_not_found / not_found | Prompt or run is unpublished, deleted, or not yours. |
| 429 | rate_limited | 30 runs/min cap; retryAfterSeconds in the body and Retry-After. |
| 429 | over_quota | Modality quota exhausted; used + limit in the body. |
| 502 | enqueue_failed | Worker queue unreachable. Retry the request. |
Drop this into a Node 18+ script. Sets up the auth header once and dispatches a run, then polls for completion.
const TOKEN = process.env.ARTI_TOKEN!;
async function arti<T>(path: string, init: RequestInit = {}): Promise<T> {
const res = await fetch(`https://artinstring.com${path}`, {
...init,
headers: { Authorization: `Bearer ${TOKEN}`, ...(init.headers ?? {}) },
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
return res.json() as Promise<T>;
}
const { runId } = await arti<{ runId: string }>("/api/v1/runs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ promptId: "<uuid>", vars: { subject: "stoic owl" } }),
});
while (true) {
const run = await arti<{ status: string; output?: { url: string } }>(
`/api/v1/runs/${runId}`,
);
if (run.status === "completed") { console.log(run.output?.url); break; }
if (run.status === "failed") throw new Error("run failed");
await new Promise((r) => setTimeout(r, 1500));
}Same flow with the requests library. Run with ARTI_TOKEN=arti_… python script.py.
import os, time, requests
TOKEN = os.environ["ARTI_TOKEN"]
H = {"Authorization": f"Bearer {TOKEN}"}
BASE = "https://artinstring.com"
run = requests.post(
f"{BASE}/api/v1/runs",
headers={**H, "Content-Type": "application/json"},
json={"promptId": "<uuid>", "vars": {"subject": "stoic owl"}},
).json()
while True:
r = requests.get(f"{BASE}/api/v1/runs/{run['runId']}", headers=H).json()
if r["status"] == "completed":
print(r["output"]["url"])
break
if r["status"] == "failed":
raise RuntimeError("run failed")
time.sleep(1.5)The /api/v1/* namespace is versioned. We'll ship a /api/v2/* in parallel before changing field names or response shapes; v1 stays available for at least six months after v2 lands.
Found a bug or want a missing endpoint? [email protected].