API keys — secret & publishable
Workspace API keys, the client-safe publishable kind, per-key skill allowlists, and daily spend caps.
Every programmatic call to Puras authenticates with a workspace API key sent as a bearer token:
Authorization: Bearer <key>
Keys are minted in the dashboard under API keys. The secret part is shown once at creation; only a hash is stored. There are two kinds:
| Secret key | Publishable key | |
|---|---|---|
| Format | puras_live_<prefix>.<secret> | puras_pub_<prefix>.<secret> |
| Lives in | your backend, CI, CLI | your mobile/web app bundle |
| Can call | the whole API | job submit + the jobs it submitted |
| Skill allowlist | optional | optional (recommended) |
| Daily spend cap | optional | optional (recommended) |
Secret keys
Full programmatic access to your workspace: deploy skillpacks, manage secrets, read any job, query balance. Treat them like passwords — server-side only, never in an app bundle or browser code.
Publishable keys
Built for the case where your app calls Puras directly — an Expo app generating a video from a user's photo, a React site running a skill on demand — without you standing up a proxy backend.
A publishable key can only:
- submit jobs —
POST /v1/jobs(the platform's CORS is open, so browser calls work); - read the jobs it submitted —
GET /v1/jobs/{id}, plus that job's/eventsand/stream. It cannot list your workspace's job history, and other jobs return 404.
Everything else — drive, skillpacks, secrets, billing — answers 403.
Anything shipped inside an app can be extracted, so a publishable key's real security model is bounding what a leaked key can do. Two fences are set at creation and enforced on every submit:
Skill allowlist
Either all skills or an explicit list. The list can include your own skills and public marketplace skills. Matching is by skillpack + skill — a slug rename neither widens nor breaks the grant. Submitting anything outside the list returns:
403 this API key is not allowed to run `<skill>` — its skill allowlist was
set when the key was created
Daily spend cap
A per-key USD ceiling per UTC day. Once the day's jobs submitted with the key
have cost that much, further submits return 429 until 00:00 UTC. The cap
bounds blast radius, not exact billing — a job admitted just under the cap
still runs to completion.
Tip: set the cap to a small multiple of your app's expected daily usage. A leaked key is then a bounded nuisance instead of a drained balance.
The allowlist and cap can also be set on secret keys as defense in depth; the kind only decides which API surface the key may touch.
Calling skills with a key
From JavaScript (JS SDK reference):
import { Puras } from "puras";
const puras = new Puras({ apiKey: "puras_pub_…" });
const result = await puras.run("acme/ugc-ads/ugc-ad", { product: photoUrl });
From Python (SDK client reference):
import puras
client = puras.Client(api_key="puras_live_…")
result = client.run("acme/ugc-ads/ugc-ad", {"product": photo_url})
Raw HTTP:
curl -X POST "https://api.puras.co/v1/jobs?skillpack=acme/ugc-ads" \
-H "Authorization: Bearer puras_pub_…" \
-H "Content-Type: application/json" \
-d '{"skill": "ugc-ad", "inputs": {"product": "https://…"}}'
Revocation
Revoking a key (dashboard → API keys → Revoke) takes effect immediately;
running jobs finish, new requests fail with 401. Rotate by creating the new
key first, shipping it, then revoking the old one.