Overview

What Puras is, the core concepts (skills, skillpacks, jobs), and how you build and call them.

Puras is a multi-tenant agentic Backend-as-a-Service. You write a skill, deploy it, and run it as jobs from any client with a workspace API key — and when you need to, you can ship several skills together as one bundle (a skillpack). The agent loop, tool execution, file storage, billing, and observability are the platform's job — you write the skill and call it.

It fits work where a single model call isn't enough: jobs that need to plan, use tools, and iterate — research a topic and produce a deliverable, pull data out of a stack of PDFs, generate and edit media in multiple steps. Puras runs that work server-side — long-running, retryable, observable — instead of in the browser.

The core idea

skill          →  one capability (an agent prompt, or a Python function) — the unit you deploy
job            →  one run of a skill, submitted with inputs, billed by usage
skillpack      →  optional: several related skills shipped as one versioned bundle

You author a skill locally, deploy it (via the CLI or the MCP server), then submit jobs from your app, a script, or your coding agent. Most of the time that's a single skill; the skillpack is the grouping you reach for when several skills ship and version together.

Skill

A skill is the unit of work: a directory containing a skill.yaml. Its entrypoint decides how it runs — that single field is the only difference between the two kinds:

  • Agenticentrypoint: SKILL.md (any .md file). The file is loaded as the system prompt and the worker runs an autonomous LLM tool-use loop. The model drives: it can call bash, generate_image, web_search, and any built-in tools, plus Python tools you declare. Reach for this when the path isn't fixed and you want the agent to figure it out.

  • Deterministicentrypoint: main.py:run. The worker imports your module and calls run(**inputs) in an isolated subprocess. You drive the flow in plain Python — and your code can still call LLMs and media models through puras.media. Reach for this when the steps are known and you want them exact.

Same submission API, same billing, same observability for both. A skill declares its input_schema (validated before the run) and output_schema (validated after), written in the Puras schema dialect — see the skill.yaml reference for every field and type.

yaml
# ad-creative/skill.yaml
title: Ad Creative
description: Turn a product brief into a short video ad.
entrypoint: SKILL.md          # agentic — or "main.py:run" for deterministic
text_model: claude/opus-4-8   # optional, agentic only
input_schema:
  type: object
  properties:
    brief: { type: string }
    product_image: { type: image }
  required: [brief, product_image]
output_schema:
  type: object
  properties:
    video: { type: video }

Deploying — one skill, or a pack

The thing you push is a zip of a directory. The simplest case is one skill — a single folder with a skill.yaml — and that's the default: puras deploy in that directory auto-creates the deployment on first run and pushes it.

A skillpack is the optional grouping for shipping several related skills together as one versioned bundle. It's a zip whose top-level folders are the skills: every immediate child of the bundle root that has a skill.yaml is auto-discovered as a skill, and the folder name is the skill name. There is no skills/ wrapper and no root manifest — adding a skill to the pack means adding a top-level directory.

my-skillpack/                 # the bundle root (a pack of several skills)
  ad-creative/                # each top-level dir with a skill.yaml is a skill
    skill.yaml
    SKILL.md                  # agentic system prompt
  image-info/
    skill.yaml
    main.py                   # deterministic entrypoint
    requirements.txt          # optional — extra pip deps for THIS skill's venv

A deployment is one push of the bundle, versioned. Activating a new deployment is a rolling switch: new jobs use the active version; jobs already running finish on the version they started with.

Job

A job is one run of a skill. Submit the skill name and inputs in the body, and name the target deployment in the query string (the ?skillpack= param names the bundle — a single skill or a pack):

json
POST /v1/jobs?skillpack=<workspace>/<skillpack_slug>
{ "skill": "ad-creative", "inputs": { "brief": "...", "product_image": "..." } }

A skill's full path — the one on its page — is workspace/skillpack_slug/skill. Drop the trailing skill name into the skill body field and pass what's left, workspace/skillpack_slug, as ?skillpack=. For a deployment in your own workspace, just ?skillpack=<slug> works; a <uuid> is also accepted, and the legacy ?skillpack_id=<uuid> still works.

The worker reads the named skill from the active deployment and dispatches to the agent loop or the deterministic runner based on its entrypoint — there's no type to pass. Lifecycle: queued → running → succeeded | failed | cancelled. The response (JobOut) carries status, result, and error.

One endpoint, three call modes:

ModeRequestYou getUse when
asyncPOST /v1/jobsthe job immediately, status: queuedfire-and-forget; poll GET /v1/jobs/{id}
sync…?wait=true&timeout=N (1–60s)the job once terminal, or the current row at timeoutshort jobs
stream…?stream=truean SSE stream of live eventslong agentic jobs you want to watch

Workspace, keys, drive, secrets

  • Workspace — the unit of tenancy. One per user. Holds API keys, the drive, credit balance, jobs, and your deployed skills.
  • API key — format puras_live_<prefix>.<secret> for secret keys, puras_pub_<prefix>.<secret> for client-app-safe publishable keys (the dot is part of the key — don't strip it). Pass as Authorization: Bearer <key>; mint one in the dashboard. Keys are workspace-scoped, so you name the target deployment per request via skillpack (a workspace/skillpack_slug slug path, or just the slug for your own workspace, or its UUID). See API keys for kinds, skill allowlists, and spend caps.
  • Drive — workspace-wide private file storage. Skills read and write it as plain files at ./drive/; apps push files in via POST /v1/drive/upload and get back a drive_path.
  • Secrets — deployment-scoped, env-var-style key/values (^[A-Z_][A-Z0-9_]*$), encrypted at rest, never returned by the API. Injected as env vars into the skill subprocess at run time.

Billing

You're charged per job for what it uses: LLM tokens and media generation, billed to the cent, plus a flat 5% platform fee. A job is only picked up while your workspace has positive credit, and while it runs it can spend through. GET /v1/jobs/{job_id}/usage gives the line-item breakdown. See the pricing page for rates.

Two ways to build and call

You never touch the agent loop or infrastructure directly — you work through one of two surfaces:

  • MCP server (mcp.puras.co) — connect your coding agent (Claude Code, Cursor) in one OAuth command and deploy, run, and tail jobs from your editor. See Puras MCP tools.
  • puras CLI + Python SDK (pip install puras) — deploy and run from your terminal or CI with the CLI, and call deployed skills from your own app with puras.Client (SDK client reference).

Inside a skill, the runtime SDK (from puras import media, secret) gives you media generation, secrets, and file inputs with no API key — the worker injects the job context.

Where to go next