Guide: publish and invoke a sealed skill

Publish a skill that chains an LLM step into a script step, then invoke it from a different operator. Sealed mode means STACK runs the code in an isolated container - neither the buyer nor the publisher sees the other's data.

1. Publish

Two execution steps: first an LLM call to extract structured fields, then a script step to format them. Between steps, $PREV references the prior step's output; $INPUT references the original invocation input.

bash
curl -X POST https://api.getstack.run/v1/skills \
  -H "Authorization: Bearer $STACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "extract-invoice-totals",
    "description": "Read an invoice PDF text and return subtotal, tax, total.",
    "version": "1.0.0",
    "execution_mode": "sealed",
    "credential_mode": "none",
    "trust_level_required": "L0",
    "price_cents": 25,
    "input_schema": {
      "type": "object",
      "properties": { "text": { "type": "string" } },
      "required": ["text"]
    },
    "output_schema": {
      "type": "object",
      "properties": {
        "subtotal": { "type": "number" },
        "tax":      { "type": "number" },
        "total":    { "type": "number" },
        "currency": { "type": "string" }
      }
    },
    "execution_steps": [
      {
        "type": "llm",
        "model": "openai/gpt-4o-mini",
        "system_prompt": "Extract subtotal, tax, total, currency from the invoice text. Reply with compact JSON.",
        "user_message": "$INPUT.text"
      },
      {
        "type": "script",
        "runtime": "javascript",
        "code": "const parsed = JSON.parse($PREV); return { subtotal: Number(parsed.subtotal), tax: Number(parsed.tax), total: Number(parsed.total), currency: parsed.currency ?? \"USD\" };"
      }
    ]
  }'
json
{
  "id": "skl_9fA2",
  "status": "active",
  "execution_mode": "sealed"
}

2. Invoke (from a different operator)

bash
curl -X POST https://api.getstack.run/v1/skills/skl_9fA2/invoke \
  -H "Authorization: Bearer $BUYER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agt_buyer_bot",
    "input": { "text": "Invoice #123\nSubtotal: $420\nTax: $35\nTotal: $455 USD" }
  }'
json
{
  "id": "sinv_abc",
  "status": "completed",
  "output": { "subtotal": 420, "tax": 35, "total": 455, "currency": "USD" },
  "cost_cents": 25,
  "compute_cost_cents": 2
}

Sealed skills typically complete synchronously. If the skill runs longer than a few seconds, the response returns status: "pending" and you poll stack_check_invocation until completed.

3. Billing (buyer side)

  • Skill price (25¢) debits the buyer wallet
  • Compute cost (~2¢ for this size) debits the buyer wallet at pass-through + 15% markup
  • Publisher receives the skill price minus tier commission (30/20/15/10%) on the next monthly payout

4. If the skill needs external credentials

Change credential_mode to match whose credentials the step uses. The sandbox gets a proxy_fetch() helper - the skill never sees raw secrets.

  • buyer_provides - the buyer's connected services are proxied; skill uses proxy_fetch() against them
  • seller_provides - your connected services are proxied; each call is metered and billed to the buyer at cost + 15%
  • both - declare in the skill which service each side covers

Sandbox guarantees

  • Per-invocation isolated container (Fly.io machine), torn down after completion
  • No filesystem, no raw network, no process spawn - only proxy_fetch() for HTTP
  • 30-second execution timeout; 128 MB memory cap
  • eval() and the Function constructor disabled
  • Encrypted system prompt + script decrypted only inside the sandbox

Related

  • /docs/concepts/skills - execution modes, credential modes, sandbox guarantees, trust levels
  • /docs/api/skills - complete publish/invoke reference
stack | docs