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 keyPublishable key
Formatpuras_live_<prefix>.<secret>puras_pub_<prefix>.<secret>
Lives inyour backend, CI, CLIyour mobile/web app bundle
Can callthe whole APIjob submit + the jobs it submitted
Skill allowlistoptionaloptional (recommended)
Daily spend capoptionaloptional (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 jobsPOST /v1/jobs (the platform's CORS is open, so browser calls work);
  • read the jobs it submittedGET /v1/jobs/{id}, plus that job's /events and /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):

ts
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):

python
import puras

client = puras.Client(api_key="puras_live_…")
result = client.run("acme/ugc-ads/ugc-ad", {"product": photo_url})

Raw HTTP:

bash
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.