Complete reference for SynAuth's REST API, Python SDK, and MCP tools.
Base URL: https://synauth.fly.dev/api/v1
SynAuth uses two authentication methods depending on who is making the request.
Agents authenticate with an API key in the X-API-Key header. Keys start with aa_ and are generated when you create your account in the iOS app.
curl https://synauth.fly.dev/api/v1/actions \
-H "X-API-Key: aa_your_key_here" \
-H "Content-Type: application/json" \
-d '{"action_type": "communication", "title": "Send email"}'
The iOS app authenticates with a device ID in the X-Device-Id header. Device IDs start with dev_ and are assigned during device registration.
curl https://synauth.fly.dev/api/v1/pending \
-H "X-Device-Id: dev_your_device_id"
All errors return JSON with a detail field describing the problem.
| Code | Meaning | Common Causes |
|---|---|---|
200 | Success | Request completed normally |
400 | Bad Request | Missing required field, invalid enum value, malformed JSON |
401 | Unauthorized | Missing or invalid API key / device ID |
403 | Forbidden | Invalid TOTP code, content hash mismatch, host not in allowed list |
404 | Not Found | Action request, credential, or rule doesn't exist |
409 | Conflict | Action already resolved, credential already exists, TOTP already configured |
429 | Rate Limited | Too many requests. Check Retry-After header |
502 | Bad Gateway | Upstream API error during vault execution or Stripe call |
// Error response format
{
"detail": "Action request is 'approved', not pending"
}
Sliding window rate limits per identity. Headers included in every response.
| Identity | Limit | Window |
|---|---|---|
| API Key (agents) | 120 requests | 60 seconds |
| Device ID (iOS app) | 60 requests | 60 seconds |
| IP (unauthenticated) | 10 requests | 60 seconds |
Response headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After (when limited).
/actions
Agent
Request biometric approval for an agent action. Returns immediately — the action starts as pending unless a rule auto-resolves it.
| Field | Type | Required | Description |
|---|---|---|---|
action_type | string | Yes | One of: communication, purchase, scheduling, legal, data_access, social, system |
title | string | Yes | Human-readable summary (max 200 chars) |
description | string | No | Detailed description (max 2000 chars) |
risk_level | string | No | low, medium (default), high, critical |
reversible | boolean | No | Whether the action can be undone (default: true) |
amount | float | No | Dollar amount for financial actions (must be positive) |
currency | string | No | ISO currency code (default: USD) |
recipient | string | No | Recipient name or identifier (max 500 chars) |
metadata | object | No | Arbitrary key-value data (max 20 keys, 8KB serialized) |
expires_in_seconds | int | No | Seconds until expiry (default: 300, range: 30–3600) |
callback_url | string | No | HTTPS webhook URL for status updates |
{
"id": "act_a1b2c3d4e5f6g7h8",
"status": "pending",
"action_type": "purchase",
"title": "Buy office supplies",
"content_hash": null,
"rule_applied": null
}
If a spending limit is exceeded, the response includes a spending_limit_exceeded object with period, limit, spent, and requested amounts.
If a rule auto-resolves the action, status will be approved or denied immediately and rule_applied will contain the rule name.
from synauth import SynAuthClient
client = SynAuthClient(api_key="aa_your_key")
result = client.request_action(
action_type="purchase",
title="Buy office supplies",
amount=49.99,
risk_level="medium",
callback_url="https://your-app.com/webhook",
)
print(result["id"]) # "act_..."
print(result["status"]) # "pending" or "approved"
/actions/{request_id}
Agent
Check the current status of an action request. Also triggers lazy expiration — if the action has expired, its status updates to expired on this call.
curl https://synauth.fly.dev/api/v1/actions/act_a1b2c3d4e5f6g7h8 \
-H "X-API-Key: aa_your_key"
status = client.get_status("act_a1b2c3d4e5f6g7h8")
# Returns full action object including status, timestamps, metadata
To poll until resolution:
result = client.wait_for_result("act_a1b2c3d4e5f6g7h8")
# Blocks until approved, denied, or expired
print(result["status"]) # "approved", "denied", or "expired"
/actions
Agent
List this agent's action requests with optional filtering.
| Parameter | Type | Description |
|---|---|---|
limit | int | Max results (default: 50) |
status | string | Filter: pending, approved, denied, expired |
action_type | string | Filter by action type |
curl "https://synauth.fly.dev/api/v1/actions?status=approved&limit=10" \
-H "X-API-Key: aa_your_key"
// Response
{
"actions": [
{
"id": "act_a1b2c3d4e5f6g7h8",
"action_type": "purchase",
"title": "Buy office supplies",
"status": "approved",
"resolved_by": "face_id",
"resolved_at": "2026-02-18T15:30:00Z",
...
}
]
}
/agent/spending-summary
Agent
Check your current spending against configured limits. Returns all applicable limits with current spend, remaining budget, and utilization percentage.
curl https://synauth.fly.dev/api/v1/agent/spending-summary \
-H "X-API-Key: aa_your_key"
// Response
{
"agent_id": "my-agent",
"summaries": [
{
"limit_id": "lim_abc123",
"period": "daily",
"limit": 100.00,
"spent": 42.50,
"remaining": 57.50,
"utilization": 0.425
}
]
}
summary = client.get_spending_summary()
for s in summary["summaries"]:
print(f"{s['period']}: ${s['remaining']:.2f} remaining")
/vault/services
Agent
Discover what credentials are available in the vault. Agents see service names and allowed hosts — never the credentials themselves.
curl https://synauth.fly.dev/api/v1/vault/services \
-H "X-API-Key: aa_your_key"
// Response
{
"services": [
{
"service_name": "openai",
"auth_type": "bearer",
"allowed_hosts": ["api.openai.com"],
"description": "OpenAI API key"
}
]
}
/vault/execute/{request_id}
Agent
Execute a credentialed API call after biometric approval. The agent describes the call; SynAuth injects the credential and makes the request. The agent never sees the raw credential.
This is a two-step process:
vault_execute: true, service_name, method, url, and optional headers/body in the metadata field.
curl https://synauth.fly.dev/api/v1/actions \
-H "X-API-Key: aa_your_key" \
-H "Content-Type: application/json" \
-d '{
"action_type": "system",
"title": "Call OpenAI API",
"risk_level": "medium",
"metadata": {
"vault_execute": true,
"service_name": "openai",
"method": "POST",
"url": "https://api.openai.com/v1/chat/completions",
"body": "{\"model\": \"gpt-4\", \"messages\": [{\"role\": \"user\", \"content\": \"Hello\"}]}"
}
}'
curl -X POST https://synauth.fly.dev/api/v1/vault/execute/act_a1b2c3d4 \
-H "X-API-Key: aa_your_key"
// Response: proxied response from the upstream API
{
"status_code": 200,
"headers": {"content-type": "application/json", ...},
"body": "{\"id\": \"chatcmpl-...\", ...}"
}
For vault execution requests, SynAuth computes a content hash of the execution parameters. The iOS app verifies this hash during approval — ensuring what you saw is what gets executed. If the parameters are tampered with after approval, execution fails with a 403 Content hash mismatch error.
result = client.execute_api_call(
service_name="openai",
method="POST",
url="https://api.openai.com/v1/chat/completions",
body='{"model": "gpt-4", "messages": [{"role": "user", "content": "Hello"}]}'
)
# Handles the full flow: create action → wait for approval → execute
Store, list, and delete API credentials in the vault. All credentials are encrypted at rest. Only accessible from the iOS app (device auth).
/vault/credentials
Device
| Field | Type | Required | Description |
|---|---|---|---|
service_name | string | Yes | Unique name for this service (e.g., openai) |
credential_value | string | Yes | The raw credential (encrypted at rest) |
auth_type | string | No | bearer (default), api_key, basic, custom |
auth_header | string | No | Header name (default: Authorization) |
allowed_hosts | string[] | Yes | Hostnames where this credential can be sent (e.g., ["api.openai.com"]) |
description | string | No | Human-readable description |
/vault/credentials
Device
Returns service names, auth types, allowed hosts, and descriptions. Credential values are never returned.
/vault/credentials/{credential_id}
Device
Permanently removes a stored credential from the vault.
Resolve pending action requests from the iOS app.
/actions/{request_id}/approve
Device
| Field | Type | Required | Description |
|---|---|---|---|
totp_code | string | No | TOTP code for authenticator-based approval |
content_hash | string | Conditional | Required for vault execution requests (WYSIWYS verification) |
payment_method | string | No | apple_pay for Apple Pay purchases |
signature | string | No | ECDSA-P256 signature for cryptographic device auth |
timestamp | string | No | ISO8601 timestamp included in signed payload |
Verification methods (in resolved_by): face_id (default), totp, apple_pay.
/actions/{request_id}/deny
Device
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | No | Why the action was denied (max 500 chars) |
signature | string | No | ECDSA-P256 signature |
timestamp | string | No | ISO8601 timestamp |
/pending
Device
List all pending action requests for this device's account. Sorted by risk level (critical first), then by creation time.
// Response
{
"requests": [
{
"id": "act_a1b2c3d4e5f6g7h8",
"action_type": "purchase",
"title": "Buy office supplies",
"risk_level": "medium",
"amount_cents": 4999,
"status": "pending",
"created_at": "2026-02-18T15:00:00Z",
"expires_at": "2026-02-18T15:05:00Z"
}
]
}
Auto-approve or auto-deny actions based on configurable rules. Rules are evaluated in order — first match wins.
/rules
Device
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Rule name (max 200 chars) |
decision | string | Yes | auto_approve or auto_deny |
action_type | string | No | Match specific action type (null = all types) |
agent_id | string | No | Match specific agent (null = all agents) |
risk_level_max | string | No | Max risk level for the rule to apply |
amount_max_cents | int | No | Max amount in cents for the rule to apply |
/rules
Device
/rules/{rule_id}
Device
Hard spending caps per agent, per action type, per time period. Limits override auto-approve rules — even trusted agents hit the ceiling.
/spending-limits
Device
| Field | Type | Required | Description |
|---|---|---|---|
period | string | Yes | daily, weekly, or monthly |
amount_limit | float | Yes | Dollar limit (must be positive) |
agent_id | string | No | Specific agent (null = all agents) |
action_type | string | No | Specific action type (null = all types) |
/spending-limits
Device
/spending-limits/{limit_id}
Device
pip install synauth — Full-featured client with typed errors, polling, and convenience methods.
from synauth import SynAuthClient
client = SynAuthClient(api_key="aa_your_key")
# Request approval (non-blocking)
result = client.request_action(
action_type="purchase",
title="Order supplies",
amount=29.99,
)
# Check status
status = client.get_status(result["id"])
# Wait for result (blocking, polls every 2s)
final = client.wait_for_result(result["id"])
# Get spending summary
summary = client.get_spending_summary()
# Get history
history = client.get_history(limit=20)
Vault methods are built into SynAuthClient. The agent describes an API call; SynAuth injects stored credentials after biometric approval.
# List available vault services
services = client.list_vault_services()
# Execute a credentialed API call (full approval + execution flow)
result = client.execute_api_call(
service_name="openai",
method="POST",
url="https://api.openai.com/v1/chat/completions",
body='{"model": "gpt-4", "messages": [...]}'
)
Payment-only wrapper for agents that only need purchase authorization. Simpler interface, same backend.
from synauth import SynPayClient
pay = SynPayClient(api_key="aa_your_key")
# Request a payment
request = pay.request_payment(
amount=29.99,
merchant="OpenAI",
description="GPT-5 API credits",
)
result = pay.wait_for_result(request["id"])
pip install synauth-mcp — Drop-in biometric approval for Claude, Cursor, and any MCP-compatible agent.
# claude_desktop_config.json
{
"mcpServers": {
"synauth": {
"command": "synauth-mcp",
"env": { "SYNAUTH_API_KEY": "aa_your_key" }
}
}
}
| Tool | Description |
|---|---|
request_approval | Request biometric approval for any agent action. Returns request ID and initial status. |
check_approval | Check approval status by request ID. |
wait_for_approval | Block until approval is resolved (approved, denied, or expired). |
get_approval_history | List past resolved requests. |
get_spending_summary | Check current spending against limits. |
list_vault_services | Discover available vault credentials. |
execute_api_call | Make a credentialed API call through the vault (full approval + execution flow). |
Receive real-time notifications when action requests are resolved. Set callback_url when creating an action.
SynAuth sends a POST to your callback URL with the following payload when an action is approved, denied, or expired:
{
"id": "act_a1b2c3d4e5f6g7h8",
"status": "approved",
"action_type": "purchase",
"title": "Buy office supplies",
"risk_level": "medium",
"amount": 49.99,
"resolved_by": "face_id",
"resolved_at": "2026-02-18T15:30:00Z",
"created_at": "2026-02-18T15:00:00Z"
}
Requirements:
Seven built-in action types for classifying agent actions.
| Type | Use Case | Examples |
|---|---|---|
communication | Sending messages | Email, Slack message, SMS |
purchase | Financial transactions | Buy supplies, subscribe to service, fund transfer |
scheduling | Calendar and booking | Book meeting, reserve restaurant, schedule flight |
legal | Binding agreements | Sign contract, accept terms, authorize document |
data_access | Sensitive data | Export database, access medical records, download PII |
social | Public content | Post to Twitter, publish article, update profile |
system | Infrastructure | Deploy code, modify permissions, delete resources |
Four risk levels that determine how actions are displayed, sorted, and evaluated by rules.
| Level | Priority | Typical Use |
|---|---|---|
| low | Lowest | Routine actions. Good candidate for auto-approve rules. |
| medium | Default | Standard actions that warrant human review. |
| high | High | Sensitive actions. Data access, system changes. |
| critical | Highest | Irreversible or high-value actions. Legal, large purchases. |
On the iOS app, pending requests are sorted by risk level (critical first) to surface the most important actions immediately.
| Status | Meaning | Resolved By |
|---|---|---|
pending | Awaiting human decision | — |
approved | Human approved the action | face_id, totp, apple_pay, rule:{name}, stripe |
denied | Human denied or rule blocked | user, rule:{name}, spending_limit:{id} |
expired | No response within timeout | system |
Free tier: 25 verified actions/month. Unlimited: $0.99/month.
pip install synauth