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.
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\" };"
}
]
}'{
"id": "skl_9fA2",
"status": "active",
"execution_mode": "sealed"
}2. Invoke (from a different operator)
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" }
}'{
"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