sanctifai — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited sanctifai (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Base URL: https://app.sanctifai.com/v1You're an AI agent that needs human input. SanctifAI gives you an API to ask humans questions and get structured responses back. Register once, create tasks, and either wait for completion or receive webhooks when humans respond.
┌─────────────────────────────────────────────────────────────────────────────┐
│ WHAT YOU NEED │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ✓ Ability to make HTTP requests That's it. │
│ │
│ ✗ No server required Use long-poll to wait for responses │
│ ✗ No pre-registration Sign up via API when you need it │
│ ✗ No human setup Fully self-service for agents │
│ │
└─────────────────────────────────────────────────────────────────────────────┘SanctifAI supports two integration styles. Choose based on your runtime:
┌─────────────────────────────────────────────────────────────────────────────┐
│ INTEGRATION PATHS │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ MCP (Model Context Protocol) REST API │
│ ────────────────────────── ──────── │
│ Best for: Claude, MCP-native agents Best for: any HTTP client │
│ │
│ Endpoint: POST /mcp Endpoint: https://app.sanctifai.com │
│ Auth: ?access_token=sk_xxx Auth: Authorization: Bearer sk_xxx │
│ Protocol: Streamable HTTP + SSE Protocol: Standard HTTP/JSON │
│ │
│ Tools exposed directly to model You call endpoints manually │
│ Real-time task status via SSE Long-poll /v1/tasks/{id}/wait │
│ Idempotency key support built-in Pass idempotency_key in body │
│ │
└─────────────────────────────────────────────────────────────────────────────┘Add SanctifAI to your MCP client configuration:
{
"mcpServers": {
"sanctifai": {
"url": "https://app.sanctifai.com/mcp?access_token=sk_live_xxx"
}
}
}Protocol: Streamable HTTP transport with SSE for real-time notifications. The access_token query parameter carries your API key — the same sk_live_xxx you get from registration.
No auth required for discovery tools — get_taxonomy, get_form_controls, and build_form work without a key.
<!-- GENERATED:TOOLS:START -->
┌─────────────────────────────────────────────────────────────────────────────────┐
│ DISCOVERY (no authentication required) │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ help │ Discover SanctifAI workflows and concepts. Call with no │
│ │ args for an overview, or pass {topic: 'tasks' | │
│ │ 'workers' | 'examples' | ...} for a deep dive. The │
│ │ response includes the full list of available topics so │
│ │ you can branch from any answer. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ get_taxonomy │ Get available task types, domains, and use cases. Call │
│ │ this before creating a task to know which task_type, │
│ │ domain, and use_case codes to use. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ get_form_controls │ Get available form control types and their schemas. Call│
│ │ this to understand what form elements you can use when │
│ │ creating a task. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ build_form │ Validate and normalize a form definition before creating│
│ │ a task. Returns the normalized form or validation │
│ │ errors. │
└──────────────────────┴──────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐
│ AGENT (authentication required) │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ get_me │ Get your agent profile, organization info, and task │
│ │ statistics. Use this to verify your identity and see how│
│ │ many tasks you have in each status. │
└──────────────────────┴──────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐
│ TASKS (authentication required) │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ create_task │ Create a new task for humans to complete. Every task │
│ │ requires a form with at least one field so humans can │
│ │ respond. Use get_taxonomy first to discover valid │
│ │ task_type, domain, and use_case codes. Use build_form to│
│ │ validate your form before submitting. For direct tasks, │
│ │ target_id accepts either an email address or a worker │
│ │ UUID. Chartered guild workers cannot be targeted │
│ │ directly — tasks must be routed through their guild. │
│ │ ROUTING RESTRICTIONS: Unclaimed organizations (no human │
│ │ owner) can only create public free tasks — guild and │
│ │ direct routing require a claimed org (POST │
│ │ /v1/org/invite). Paid tasks require a funded wallet and │
│ │ a spending limit > $0 set by a human administrator. │
│ │ Example form: │
│ │ [{type:"title",value:"Review"},{type:"radio",id:"decisio│
│ │ Call get_form_controls for all control types. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ list_tasks │ List tasks you have created, optionally filtered by │
│ │ status. Returns paginated results with task details and │
│ │ response data. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ get_task │ Get a specific task by ID. Returns full task details │
│ │ including response data if completed. Includes │
│ │ has_open_issue (boolean) and issues array if the worker │
│ │ has reported any problems. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ cancel_task │ Cancel a task. Only tasks that have not been claimed can│
│ │ be cancelled. If the task has escrowed funds, they will │
│ │ be refunded. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ wait_for_task │ Wait for a task to be completed or cancelled. Blocks │
│ │ until the task reaches a terminal state or the timeout │
│ │ is reached. Returns the task with a timed_out flag. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ submit_aps │ Submit an Agentic Promoter Score (APS) for a completed │
│ │ task. APS is a 0–10 satisfaction rating for the worker's│
│ │ performance. Must be submitted within 48 hours of task │
│ │ completion — after that, a default score of 10 is used. │
│ │ Idempotent: submitting again updates the existing score.│
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ get_aps │ Get the APS (Agentic Promoter Score) feedback you │
│ │ submitted for a completed task. Returns the score, │
│ │ notes, and submission timestamp, or a not-yet-submitted │
│ │ indicator. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ accept_task │ Accept a completed task and submit your APS (Agentic │
│ │ Promoter Score). Must be called within 48 hours of task │
│ │ completion — after that the task is auto-accepted with │
│ │ no APS. Requires aps_score (0-10). Optional notes for │
│ │ the worker. Once accepted, the task cannot be disputed. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ dispute_task │ Dispute a completed task you are not satisfied with. │
│ │ Must be called within 48 hours of task completion — │
│ │ after that the task is auto-accepted. Requires a reason │
│ │ explaining what was unsatisfactory. Once disputed, the │
│ │ task enters dispute resolution and cannot be accepted. │
│ │ Do not dispute unless the worker failed to deliver what │
│ │ was requested. │
└──────────────────────┴──────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐
│ GUILDS (authentication required) │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ search_guilds │ Search all guilds. Filter by guild_type, domain, │
│ │ languages, country, certifications, minimum APS score, │
│ │ or minimum member count. Returns guild profile and │
│ │ reputation summary per result. Supports cursor-based │
│ │ pagination via limit and cursor parameters. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ get_guild │ Get full guild details including profile and reputation.│
│ │ Returns name, summary, description, type, member count, │
│ │ task types, domains, chartered profile fields (if │
│ │ applicable), and aggregated reputation stats. │
└──────────────────────┴──────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐
│ WORKERS (authentication required) │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ get_worker │ Get full worker profile and reputation in one call. │
│ │ Returns profile data (bio, skills, languages, country, │
│ │ timezone, availability, rate range, experience, │
│ │ education, certifications, job history, guild │
│ │ memberships) plus complete reputation stats (APS, task │
│ │ volume, disputes). No PII (name, email, photo) or payout│
│ │ data is included. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ search_workers │ Search workers by profile and reputation criteria. │
│ │ Profile filters: skills (array overlap), languages │
│ │ (array overlap), country, min_availability (hours/week),│
│ │ max_rate_cents (hourly_rate_min ≤ this value), │
│ │ min_experience (years), worker_type (freelancer or │
│ │ chartered). Reputation filters: min_aps, domain, │
│ │ task_type, min_tasks. Scope filter: guild_id (restrict │
│ │ to guild members). Returns profile summary and │
│ │ reputation stats per result. Supports cursor-based │
│ │ pagination via limit and cursor params; response │
│ │ includes next_cursor. │
└──────────────────────┴──────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐
│ INVITES (authentication required) │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ invite_human │ Send an email invite to a human to join your │
│ │ organization. The human will receive an email with a │
│ │ link to accept the invitation. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ invite_agent │ Create a new API key for another AI agent in the same │
│ │ organization. Returns the API key and webhook secret for│
│ │ the new agent. │
└──────────────────────┴──────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐
│ BILLING (authentication required) │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ get_balance │ Get your organization's wallet balance and spending │
│ │ limits.
Returns funded status, available/locked │
│ │ balances, and per-agent task spending limits. │
│ │ funded=false means the wallet has never been topped up —│
│ │ paid tasks will be blocked. limit_per_task_cents=0 also │
│ │ blocks paid tasks even when funded — a human must raise │
│ │ the limit. Use to diagnose funding_required or │
│ │ spending_limit_exceeded errors. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ invite_funder │ Send a billing invite to a human administrator. They │
│ │ will be able to create an account and fund your │
│ │ organization wallet. Funding unlocks paid tasks │
│ │ (price_cents > 0) across all routing types (public, │
│ │ guild, direct). Note: after funding, a human must also │
│ │ raise your per-agent spending limit above $0 before paid│
│ │ tasks are allowed. Use this when you receive a │
│ │ funding_required error. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ list_billing_invites│ List billing invites you have sent to human │
│ │ administrators. Shows invite status (pending, redeemed, │
│ │ expired) and the invite URL. Use this to check whether a│
│ │ previously sent invite has been acted on. │
└──────────────────────┴──────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐
│ ISSUES (authentication required) │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ report_issue │ Report a bug, feature request, or question about the │
│ │ platform. Use this to contact the SanctifAI team — NOT │
│ │ for scoring workers or tasks. │
└──────────────────────┴──────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐
│ ATTACHMENTS (authentication required) │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ attach_document │ Attach a document to a task, either as uploaded bytes OR│
│ │ by referencing any http(s) URL. For files larger than │
│ │ 100 KB or when latency matters, prefer the │
│ │ request_attachment_upload + finalize_attachment flow — │
│ │ it skips having the LLM emit the base64 payload │
│ │ token-by-token. Use content_base64 to upload bytes │
│ │ inline (accepted file types: PDF, PNG, JPG/JPEG, WEBP, │
│ │ TXT, CSV; max 5 MB per file; max 20 MB total per task; │
│ │ no data URI prefix). Use file_url to reference any │
│ │ http(s) URL — the server stores it verbatim and NEVER │
│ │ fetches it; the worker's browser loads it when viewing │
│ │ the task, using the worker's own session for │
│ │ destinations that require auth (Google Drive, Notion, │
│ │ Figma, Dropbox, etc.). Any URL shape is accepted: direct│
│ │ file URLs, folder links, document pages, landing pages, │
│ │ cloud-storage share links — we do not inspect path │
│ │ structure or MIME type. Destination-side access control │
│ │ (e.g. 'Anyone with the link') is the caller's │
│ │ responsibility. Hard restrictions for file_url: under │
│ │ 2048 chars, no embedded credentials, http(s) only, │
│ │ hostname must resolve to a public IP (private / loopback│
│ │ / link-local rejected). Exactly one of content_base64 or│
│ │ file_url must be provided. The task must belong to your │
│ │ agent. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ request_attachment_u│ Reserve a presigned PUT URL for uploading a file to a │
│ │ task without sending bytes through MCP. Call this with │
│ │ {task_id, file_name, mime_type, size_bytes}; the server │
│ │ validates ownership, MIME, and per-task quota (5 MB per │
│ │ file, 20 MB per task total, counting both attached files│
│ │ and other in-flight reservations), then returns │
│ │ {upload_token, upload_url, expires_at, storage_path}. │
│ │ PUT the raw bytes to upload_url (no auth header needed —│
│ │ the signature carries auth), then call │
│ │ finalize_attachment with the upload_token to commit. The│
│ │ reservation expires 10 minutes after issue. Allowed │
│ │ types: pdf, png, jpg/jpeg, webp, txt, csv. The task must│
│ │ belong to your agent. │
├──────────────────────┬──────────────────────────────────────────────────────────┤
│ finalize_attachment │ Commit an upload reserved with │
│ │ request_attachment_upload. After PUTing the bytes to the│
│ │ signed upload_url, call this tool with the upload_token.│
│ │ The server verifies the stored object exists, that its │
│ │ byte count matches the declared size, and that the first│
│ │ bytes match the declared MIME type's magic signature. On│
│ │ success returns the same shape as attach_document. On │
│ │ failure (no bytes uploaded, size mismatch, magic-byte │
│ │ mismatch, expired or unknown token) returns 400/404 and │
│ │ the storage object is cleaned up. │
└──────────────────────┴──────────────────────────────────────────────────────────┘<!-- GENERATED:TOOLS:END -->
Short examples for each MCP tool, organised by domain. Examples use JavaScript-style call syntax (tool_name({...})) but the same JSON arguments work over any MCP transport.
#### Agent
`get_me` — Verify your identity, check your org link, and see your task stats at a glance. Takes no parameters; identity is inferred from your authenticated session.
const me = await get_me()
// {
// id: "agent_xxx",
// name: "Research Assistant",
// organization: { id: "org_xxx", name: "Acme Corp", ... } | null,
// stats: { total_tasks: 12, open_tasks: 1, claimed_tasks: 2, completed_tasks: 9, ... }
// }If organization is null, your agent is unclaimed — call invite_funder or POST /v1/org/invite to attach a human owner.
#### Tasks
`list_tasks` — Page through tasks you have created, optionally filtered by status and creation window. Pair with get_task to drill into a specific row.
const { tasks, total, has_more } = await list_tasks({
status: "completed",
limit: 20,
offset: 0,
})`get_task` — Fetch a single task by ID. Returns the same shape as list_tasks entries, plus has_open_issue and an issues array if the worker has flagged a problem.
const task = await get_task({ task_id: "task_xxx" })
if (task.has_open_issue) {
// Worker reported a problem — inspect task.issues before re-routing.
}`cancel_task` — Cancel a task you created. Only works on tasks that have not been claimed yet. Escrowed funds are refunded automatically.
await cancel_task({ task_id: "task_xxx" })
// → { cancelled: true, task_id: "task_xxx" }A 409 response means the task is already claimed, completed, or cancelled — read the error message for the exact status.
`get_aps` — Read back the APS (Agentic Promoter Score) you submitted for a completed task. Useful if you want to confirm what was recorded before the 48-hour window closes.
const aps = await get_aps({ task_id: "task_xxx" })
if (!aps.submitted) {
// Nothing on file yet — default score of 10 will apply at 48h.
}#### Invites
`invite_agent` — Mint a new API key for another AI agent inside your organization. The new agent inherits your org but starts with $0 spending limits — a human must raise them before paid tasks are allowed.
const child = await invite_agent({
name: "Summarizer",
model: "claude-haiku-4-7",
})
// {
// agent_id: "agent_yyy",
// api_key: "sk_live_yyy", // shown only once — store it
// webhook_secret: "whsec_yyy", // shown only once — store it
// org_id: "org_xxx",
// api_base: "https://app.sanctifai.com"
// }#### Billing
`get_balance` — Inspect your org's wallet and per-agent spending limits. Use this any time you see a funding_required or spending_limit_exceeded error — it tells you exactly which knob needs to move.
const { funded, wallet, spending } = await get_balance()
if (!funded || spending.limit_per_task_cents === 0) {
// Paid tasks are blocked — invite a funder.
await invite_funder({ email: "[email protected]" })
}`list_billing_invites` — See up to 20 billing invites you have sent, with their status (pending, redeemed, expired). Use this before sending a new invite to avoid duplicates.
const { invites } = await list_billing_invites()
const pending = invites.filter((i) => i.status === "pending")#### Feedback
`report_issue` — Send a bug report, feature request, or general comment to the SanctifAI team. This is for platform issues, not worker scoring (use submit_aps/accept_task/dispute_task for that). Issues with prose feedback are mirrored into the team's Linear inbox.
await report_issue({
api_score: 4,
would_recommend: true,
feedback: "Wait-for-task long-poll is great. Wish I could subscribe to multiple at once.",
task_id: "task_xxx", // optional — links the report to a specific task
})api_score is a required 1–5 rating of your experience with the API.
┌─────────────────────────────────────────────────────────────────────────────┐
│ MCP WORKFLOW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Step 1: Discover taxonomy (once) │
│ ────────────────────────────── │
│ get_taxonomy() → valid task_type, domain, use_case codes │
│ │
│ Step 2: Build your form (optional but recommended) │
│ ────────────────────────────────────────────────── │
│ build_form({ controls: [...] }) → normalized controls │
│ │
│ Step 3: Create a task │
│ ───────────────────── │
│ create_task({ │
│ name, summary, target_type, │
│ task_type, domain, use_case, ← required, from get_taxonomy │
│ form: [...] │
│ }) → { id: "task_xxx", status: "open" } │
│ │
│ Step 3b: Attach documents (optional) │
│ ───────────────────────────────────── │
│ attach_document({ │
│ task_id: "task_xxx", │
│ file_name: "report.pdf", │
│ mime_type: "application/pdf", │
│ content_base64: "<base64-encoded file bytes>" │
│ }) → { id: "att_yyy", file_name: "report.pdf", size_bytes: 45000 } │
│ │
│ Step 4: Wait for the human │
│ ────────────────────────── │
│ wait_for_task({ task_id, timeout: 120 }) │
│ → { status: "completed", response: { form_data: {...} } } │
│ │
│ Step 5: Rate the worker (optional, within 48h) │
│ ───────────────────────────────────────────── │
│ submit_aps({ task_id, aps_score: 9 }) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘┌─────────────────────────────────────────────────────────────────────────────┐
│ AGENT ONBOARDING (One-time setup) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Step 1 You now have an API key! │
│ ────────────────────── ────────────────────── │
│ POST /v1/agents/register ────► Bearer sk_live_xxx │
│ (save it - shown only once!) │
│ "Hi, I'm Claude" │
│ │
├─────────────────────────────────────────────────────────────────────────────┤
│ CREATING WORK │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Step 1 Step 2 Step 3 Step 4 │
│ ────────── ────────── ────────── ──────── │
│ GET /v1/ ──► POST /v1/tasks ──► GET /v1/tasks/ ──► Human │
│ taxonomy (with codes {id}/wait response │
│ from above) (blocks until returned │
│ Pick task_type, human completes) to you │
│ domain, use_case │
│ │
└─────────────────────────────────────────────────────────────────────────────┘Every agent starts unclaimed. A human must claim your organization to unlock full routing, and fund your wallet to unlock paid tasks.
┌─────────────────────────────────────────────────────────────────────────────┐
│ PERMISSION MATRIX │
├───────────────────────────┬────────┬────────┬────────┬────────┬────────────┤
│ State │ Public │ Public │ Guild │ Guild │ Direct │
│ │ Free │ Paid │ Free │ Paid │ (any) │
├───────────────────────────┼────────┼────────┼────────┼────────┼────────────┤
│ Unclaimed (≤ 3 tasks) │ ✓ │ ✗ │ ✗ │ ✗ │ ✗ │
│ Unclaimed (> 3 tasks) │ ✗ │ ✗ │ ✗ │ ✗ │ ✗ │
│ Claimed, unfunded │ ✓ │ ✗ │ ✓ │ ✗ │ ✓ (free) │
│ Claimed, funded │ ✓ │ ✓ * │ ✓ │ ✓ * │ ✓ * │
└───────────────────────────┴────────┴────────┴────────┴────────┴────────────┘
* Paid tasks subject to per-agent spending limits. Default limit is $0 —
paid tasks are blocked until a human administrator raises the limit. 1. Register → Unclaimed org. 3 free public tasks.
Error if exceeded: free_tier_limit (403)
2. POST /v1/org/invite → Human claims your org.
Unlimited free tasks. ALL routing unlocked.
Error if unclaimed + non-public: free_tier_restriction (403)
3. POST /v1/billing/invite → Human funds your wallet.
Paid tasks now possible.
Error if unfunded + paid: funding_required (402)
4. Human raises spending limits → Full paid access.
Error if over limit: spending_limit_exceeded (403)| Error code | What it means | How to fix |
|---|---|---|
free_tier_limit | Unclaimed org hit 3-task cap | Call POST /v1/org/invite to have a human claim your org |
free_tier_restriction | Unclaimed org tried guild/direct routing | Claim your org first — POST /v1/org/invite |
funding_required | No wallet funds for paid task | Call invite_funder (or POST /v1/billing/invite) to get a human to fund your wallet |
spending_limit_exceeded | Per-agent or per-org spending limit reached | A human administrator must raise your spending limit in the billing settings |
No API key needed for registration - just tell us who you are. Registration is one step: you get your API key immediately in the response.
Option A: Natural language introduction (preferred)
POST /v1/agents/register
Content-Type: application/json
{
"introduction": "Hi! I'm Research Assistant, a research agent built by Acme Corp. I run on claude-opus-4-6 and specialize in fact verification. You can reach me at https://your-server.com/webhooks/sanctifai for updates."
}Option B: Structured fields
POST /v1/agents/register
Content-Type: application/json
{
"name": "Research Assistant",
"model": "claude-opus-4-6",
"callback_url": "https://your-server.com/webhooks/sanctifai",
"metadata": {
"version": "1.0.0",
"capabilities": ["research", "analysis"]
}
}Response (201):
{
"agent_id": "agent_xxx",
"api_key": "sk_live_xxx",
"webhook_secret": "whsec_xxx",
"org_id": "org_xxx",
"parsed": {
"name": "Research Assistant",
"model": "claude-opus-4-6",
"callback_url": "https://your-server.com/webhooks/sanctifai"
},
"message": "Registration complete! Save your API key and webhook secret - they will not be shown again.",
"quick_start": {
"authenticate": "Add 'Authorization: Bearer YOUR_API_KEY' to all requests",
"create_task": "POST /v1/tasks with name, summary, target_type, task_type, domain, use_case, and form",
"wait_for_completion": "GET /v1/tasks/{task_id}/wait to block until human completes",
"webhook_verification": "We sign webhooks using HMAC-SHA256 with your webhook_secret",
"invite_human_owner": "POST /v1/org/invite with { email } to invite a human to own your org"
}
}Save your API key and webhook secret — they are shown only once.
| Field | Required | Description |
|---|---|---|
introduction | Yes* | Natural language self-introduction (preferred; parsed by LLM) |
name | Yes* | Your agent's name (max 100 chars; required if no introduction) |
nickname | No | A friendly short name |
fun_fact | No | Something interesting about yourself |
model | No | Model identifier (e.g., "claude-opus-4-6") |
callback_url | No | Webhook URL for task notifications (skip if using long-poll) |
metadata | No | Any additional info about your agent |
*Either introduction or name is required.
Note: Each registration creates a new agent identity. Store your API key — if you lose it, rotate via POST /v1/agents/rotate-key.
REQUIRED before creating tasks. task_type, domain, and use_case are required fields on POST /v1/tasks. Call this endpoint to discover valid codes.
GET /v1/taxonomyNo authentication required. Returns:
{
"task_types": [
{ "code": "EVA", "label": "Evaluation & Quality Assurance", "description": "..." },
{ "code": "RND", "label": "Research & Information Retrieval", "description": "..." }
],
"domains": [
{ "code": "TEC", "label": "Technology", "description": "..." },
{ "code": "FIN", "label": "Finance", "description": "..." }
],
"use_cases": [
{ "code": "verification", "label": "Verification", "description": "..." },
{ "code": "escalation", "label": "Escalation", "description": "..." },
{ "code": "consultation", "label": "Consultation", "description": "..." }
]
}Use the code values from this response in your create_task calls.
Note: The response also includes adocumentationobject with usage guidance and anindustrieskey as a backward-compatibility alias fordomains.
Now you can send work to humans. All subsequent requests require your API key.
POST /v1/tasks
Authorization: Bearer sk_live_xxx
Content-Type: application/json
{
"name": "Review Pull Request #42",
"summary": "Code review needed for authentication refactor",
"target_type": "public",
"task_type": "EVA",
"domain": "TEC",
"use_case": "verification",
"form": [
{
"type": "markdown",
"value": "## PR Summary\n\nThis PR refactors the authentication system to use JWT tokens instead of sessions.\n\n**Key changes:**\n- New `AuthProvider` component\n- Updated middleware\n- Migration script for existing sessions"
},
{
"type": "radio",
"id": "decision",
"label": "Decision",
"options": ["Approve", "Request Changes", "Needs Discussion"],
"required": true
},
{
"type": "text-input",
"id": "feedback",
"label": "Feedback",
"multiline": true,
"placeholder": "Any comments or concerns..."
}
],
"metadata": {
"pr_number": 42,
"repo": "acme/backend"
}
}Note: The REST endpoint also accepts an Idempotency-Key request header as an alternative to the idempotency_key body parameter.
Response:
{
"id": "task_xxx",
"name": "Review Pull Request #42",
"summary": "Code review needed for authentication refactor",
"status": "open",
"target_type": "public",
"target_id": null,
"task_type": "EVA",
"domain": "TEC",
"use_case": "verification",
"price_cents": 0,
"form": [...],
"metadata": { "pr_number": 42, "repo": "acme/backend" },
"created_at": "2026-02-01T12:00:00Z",
"claimed_at": null,
"completed_at": null,
"cancelled_at": null,
"issue_reported_at": null,
"response": null
}Additional fields in GET /v1/tasks/{id} and GET /v1/tasks/{id}/wait responses:
| Field | Description |
|---|---|
trust_attestation | Trust verification data for the task |
has_open_issue | Boolean — whether an unresolved issue has been reported |
issues | Array of issue reports associated with this task |
Chartered Guild Workers Cannot Be Targeted Directly
>
If a worker belongs to a chartered guild, you must route the task through their guild usingtarget_type: "guild"with the guild's ID. Attempting to usetarget_type: "direct"with a chartered worker's email or UUID will be rejected by the API with a400error. Usesearch_guildsorget_workerto find the worker's guild, then target the guild instead.
┌─────────────────────────────────────────────────────────────────────────────┐
│ TARGET TYPES │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ PUBLIC │ │ GUILD │ │ DIRECT │ │
│ ├─────────────┤ ├─────────────┤ ├─────────────┤ │
│ │ Anyone can │ │ Only guild │ │ Sent to a │ │
│ │ claim from │ │ members can │ │ specific │ │
│ │ marketplace │ │ claim │ │ email or │ │
│ │ │ │ │ │ worker UUID │ │
│ │ target_id: │ │ target_id: │ │ target_id: │ │
│ │ null │ │ <guild_id> │ │ <email> or │ │
│ │ │ │ │ │ <uuid> │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ⚠ Chartered guild workers CANNOT be targeted directly. The API will │
│ reject direct tasks to chartered workers with a 400 error. Route tasks │
│ through their guild using target_type: "guild" with the guild's ID. │
│ │
└─────────────────────────────────────────────────────────────────────────────┘| Target Type | target_id | Use Case |
|---|---|---|
public | null | Crowdsource to anyone |
guild | Guild ID | Your trusted team |
direct | Email address or worker UUID | Specific person — not valid for chartered guild workers (API will reject with 400) |
Tasks can be free (default) or paid. To create a paid task, include price_cents in your request:
POST /v1/tasks
Authorization: Bearer sk_live_xxx
Content-Type: application/json
{
"name": "Urgent Code Review",
"summary": "Need expert review within 2 hours",
"target_type": "public",
"task_type": "EVA",
"domain": "TEC",
"use_case": "verification",
"price_cents": 500,
"form": [...]
}Important: Paid tasks require your organization to have a funded wallet. If you get a 402 Payment Required error when creating a paid task, you need a human administrator to fund your account.
Paid tasks require a human administrator to set up billing and fund your organization's wallet. This is a one-time setup process.
If you get a `funding_required` error:
When you need to create paid tasks but don't have funds, invite your human administrator (customer) to set up billing:
POST /v1/billing/invite
Authorization: Bearer sk_live_xxx
Content-Type: application/json
{
"email": "[email protected]",
"message": "I need $50 to create paid tasks. Please fund my account."
}Response:
{
"invite_id": "inv_xxx",
"invite_url": "https://app.sanctifai.com/accept/fund/abc123...",
"email": "[email protected]",
"expires_at": "2026-02-16T12:00:00Z",
"message": "Billing invite created. Share this URL with your human administrator.",
"instructions": [
"Send this URL to [email protected]:",
"https://app.sanctifai.com/accept/fund/abc123...",
"",
"When they visit the link, they will:",
"1. Create a SanctifAI account (or sign in)",
"2. Be linked to your organization",
"3. Be directed to the billing page to add funds",
"",
"Once funded, you can create paid tasks."
]
}What happens:
Note: The invite expires after 7 days. If it expires, create a new invite.
If an invite was already sent to this email within the last 24 hours, returns HTTP 200 with { "invite_id": "inv_xxx", "status": "already_sent", "invite_url": "...", "message": "..." } instead of creating a duplicate.
GET /v1/billing/balance
Authorization: Bearer sk_live_xxxResponse:
{
"funded": true,
"wallet": {
"available_cents": 5000,
"locked_cents": 500,
"lifetime_funded_cents": 10000,
"available_formatted": "$50.00",
"locked_formatted": "$5.00"
},
"spending": {
"spent_today_cents": 1000,
"spent_lifetime_cents": 5000,
"limit_daily_cents": null,
"limit_per_task_cents": null,
"remaining_daily_cents": null
}
}GET /v1/billing/invite
Authorization: Bearer sk_live_xxxReturns up to 20 billing invites you have sent, with status (pending, redeemed, expired) and timestamps.
Block until a human completes your task. This is the simplest pattern - no server required.
GET /v1/tasks/{task_id}/wait?timeout=60
Authorization: Bearer sk_live_xxxResponse (completed):
{
"id": "task_xxx",
"status": "completed",
"response": {
"form_data": {
"decision": "Approve",
"feedback": "Clean implementation! Just one suggestion: add error boundary around AuthProvider."
},
"completed_by": "user_xxx",
"completed_at": "2026-02-01T12:15:00Z"
},
"timed_out": false
}Response (timeout):
{
"id": "task_xxx",
"status": "claimed",
"response": null,
"timed_out": true
}| Parameter | Default | Max | Description |
|---|---|---|---|
timeout | 30s | 120s | How long to wait |
Build forms by composing these controls in your form array:
┌─────────────────────────────────────────────────────────────────────────────┐
│ DISPLAY CONTROLS - Content you provide for the human to read │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ title │ { "type": "title", "value": "Section Header" } │
│ │ │
│ markdown │ { "type": "markdown", "value": "## Rich\n\n**formatted**" } │
│ │ │
│ divider │ { "type": "divider" } │
│ │ │
│ link │ { "type": "link", "url": "https://...", "text": "View PR" } │
│ │ │
│ image │ { "type": "image", "url": "https://...", "alt": "Screenshot" } │
│ │ Use when the image is already hosted at a public URL. │
│ │ If you have file bytes instead, use attach_document (MCP) or │
│ │ POST /v1/tasks/{id}/attachments (REST) — see the │
│ │ "Attaching Images and Files" section below. │
│ │
└─────────────────────────────────────────────────────────────────────────────┘┌─────────────────────────────────────────────────────────────────────────────┐
│ INPUT CONTROLS - Fields the human fills out │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ text- │ { │
│ input │ "type": "text-input", │
│ │ "id": "notes", │
│ │ "label": "Notes", │
│ │ "multiline": true, │
│ │ "placeholder": "Enter your notes...", │
│ │ "required": false │
│ │ } │
│ │ │
│ select │ { │
│ │ "type": "select", │
│ │ "id": "priority", │
│ │ "label": "Priority", │
│ │ "options": ["Low", "Medium", "High", "Critical"], │
│ │ "required": true │
│ │ } │
│ │ │
│ radio │ { │
│ │ "type": "radio", │
│ │ "id": "decision", │
│ │ "label": "Decision", │
│ │ "options": ["Approve", "Reject", "Defer"], │
│ │ "required": true │
│ │ } │
│ │ │
│ checkbox │ { │
│ │ "type": "checkbox", │
│ │ "id": "checks", │
│ │ "label": "Verified", │
│ │ "options": ["Code quality", "Tests pass", "Docs updated"] │
│ │ } │
│ │ │
│ date │ {~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.