API Reference

Base URL

https://perceptron.cloud/api/

Authentication

Perceptron uses two token types. Include one as a bearer token on every protected request.

Authorization: Bearer YOUR_TOKEN

Master Key: a pct-m-<64_hex> string (70 chars) obtained by signing a wallet challenge via POST /api/auth/verify. Required for key management, metrics, and master-key-only operations. Treat it like a root credential.

Client Key: a pct-<64_hex> string (68 chars) generated via POST /api/keys/generate (requires master key). Use this for inference, credits, feeds, domains, and points. Safe to hand to an agent.

Monetary convention: all credit amounts in request and response bodies are integer cents (i64). 2500 means $25.00. 1 credit = $1 USD = 100 cents.


Health

GET /api/health No Auth

Returns server status and current version. Useful for liveness checks.

{
  "status": "ok",
  "version": "1.2.6"
}
curl https://perceptron.cloud/api/health
import requests

response = requests.get("https://perceptron.cloud/api/health")
print(response.json())

Version

Release information endpoints. No authentication required.

GET /api/version/latest No Auth

Returns the latest release record, determined by highest semantic version.

{
  "version": "1.2.6",
  "release_notes": "## 1.2.6\n\n- Added version check and update modal\n- Fixed tunnel reconnect backoff\n- Improved error handling in domain proxy",
  "downloads": [
    "https://perceptron.cloud/static/release/perceptron-1.2.6-x86_64-linux.tar.gz"
  ],
  "published_at": 1748000000
}
Field Type Description
version string Semver version string (e.g. “1.2.6”)
release_notes string Raw markdown release notes
downloads array Platform-specific tarball download URLs
published_at uint64 Unix timestamp (seconds) of publication
404“no releases found”
curl https://perceptron.cloud/api/version/latest
import requests

response = requests.get("https://perceptron.cloud/api/version/latest")
print(response.json()["version"])

GET /api/version/{version} No Auth

Returns a specific release record by semver string.

Parameter Type Description
version string Semver version string, e.g. 1.2.6

Same shape as GET /api/version/latest.

404“release ‘1.2.6’ not found”
curl https://perceptron.cloud/api/version/1.2.6
import requests

response = requests.get("https://perceptron.cloud/api/version/1.2.6")
print(response.json()["release_notes"])

Authentication

GET /api/auth/challenge No Auth

Generates a one-time nonce that must be signed by your EVM wallet. The nonce expires after 5 minutes. Pass it to POST /api/auth/verify.

{
  "nonce": "a3f2e1d0-...",
  "expires_at": 1748000300
}
Field Type Description
nonce string UUID to sign with your wallet’s private key
expires_at uint64 Unix timestamp after which the nonce is invalid
curl https://perceptron.cloud/api/auth/challenge
import requests

response = requests.get("https://perceptron.cloud/api/auth/challenge")
print(response.json())
# {"nonce": "a3f2e1d0-...", "expires_at": 1748000300}

POST /api/auth/verify No Auth

Signs a challenge nonce with an EVM wallet (personal_sign, secp256k1) and returns a master key. The master key acts as your bearer token for all subsequent privileged calls. Store it securely. It is not recoverable, but you can regenerate it.

Field Type Required Description
address string yes EVM wallet address (0x…)
signature string yes 65-byte secp256k1 personal_sign signature as a hex string
nonce string yes Nonce UUID from GET /api/auth/challenge
{
  "master_key": "pct-m-a1b2c3d4e5f6...",
  "address": "0xabcdef..."
}
Field Type Description
master_key string pct-m- prefixed bearer token (70 chars). Use as Authorization: Bearer <master_key>
address string Lowercase recovered wallet address
401“invalid or expired nonce”
401“challenge expired”
401“signature verification failed”
401“signature does not match claimed address”

POST /api/auth/regenerate-master-key Master Key Only

Revokes the current master key and issues a new one. All existing client API keys (pct-...) remain valid. No request body required.

{
  "master_key": "pct-m-f9e8d7c6b5a4...",
  "address": "0xabcdef..."
}

Chat Completions

POST /api/v1/chat/completions  ·  /api/chat/completions Client or Master Key

OpenAI-compatible chat completions. Both paths behave identically. Supports streaming via SSE. Billed per token at per-model rates, deducted from your credit balance.

Field Type Required Description
model string yes Model ID from GET /api/models
messages array yes Array of message objects, see below
messages[].role string yes “system”, “user”, “assistant”, or “tool”
messages[].content string yes Message text
stream bool no Default false. Set true for SSE streaming
temperature float no Default 0.7
top_p float no Default 0.9
max_tokens uint64 no Clamped to the model’s max_output_tokens and available context
max_completion_tokens uint64 no Alias for max_tokens, same clamping logic
frequency_penalty float no Default 0.1
presence_penalty float no Passed to the model if provided
top_k any no Passed to the model if provided
stop any no Passed to the model if provided
seed any no Passed to the model if provided
n any no Passed to the model if provided
tools array no Tool definitions. Only accepted if the model supports function calling
tool_choice any no Only accepted if the model supports function calling
{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 1748000000,
  "model": "moonshotai/kimi-k2.6",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "The answer is 42."
    },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 24,
    "completion_tokens": 8,
    "total_tokens": 32
  }
}

Returns Content-Type: text/event-stream. Each event is a chat.completion.chunk:

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1748000000,"model":"moonshotai/kimi-k2.6","choices":[{"index":0,"delta":{"role":"assistant","content":"The "},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","choices":[{"index":0,"delta":{"content":"answer is 42."},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":24,"completion_tokens":8,"total_tokens":32}}

data: [DONE]

If credits run out mid-stream:

data: {"error":{"message":"credit limit reached","type":"insufficient_credits"},"credits_exhausted":true}

data: [DONE]
400“invalid model ‘…’. Available models: …”
402“insufficient credits (need at least $0.05 for this request)”
502“inference service unavailable”
500“inference request failed”
curl https://perceptron.cloud/api/v1/chat/completions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "moonshotai/kimi-k2.6",
    "messages": [
      {"role": "user", "content": "What is the capital of France?"}
    ],
    "stream": false
  }'
import requests, json

response = requests.post(
    "https://perceptron.cloud/api/v1/chat/completions",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={
        "model": "moonshotai/kimi-k2.6",
        "messages": [{"role": "user", "content": "What is the capital of France?"}],
        "stream": False,
    },
)
print(json.dumps(response.json(), indent=2))

GET /api/models No Auth

Returns all active models with pricing and capability metadata.

{
  "object": "list",
  "data": [
    {
      "id": "moonshotai/kimi-k2.6",
      "name": "Kimi K2.6",
      "object": "model",
      "created": 1748000000,
      "owned_by": "perceptron",
      "context_length": 262144,
      "max_output_tokens": 183500,
      "frontier_rank": 7,
      "pricing": {
        "input_cents_per_1m": 27,
        "output_cents_per_1m": 380
      }
    }
  ]
}
Field Type Description
id string Model ID to use in chat requests
context_length uint32 Maximum total tokens (input + output)
max_output_tokens uint32 Maximum output tokens per request
frontier_rank uint32 Capability ranking (lower = more capable)
pricing.input_cents_per_1m int64 Cost in cents per 1 million input tokens
pricing.output_cents_per_1m int64 Cost in cents per 1 million output tokens
curl https://perceptron.cloud/api/models
import requests

response = requests.get("https://perceptron.cloud/api/models")
for model in response.json()["data"]:
    print(model["id"], model["pricing"])

Credits

GET /api/credits/balance Client or Master Key

Returns the current credit balance in cents.

{
  "balance": 2500,
  "currency": "USD_CENTS"
}
Field Type Description
balance int64 Current balance in cents. 2500 = $25.00
currency string Always “USD_CENTS”
curl https://perceptron.cloud/api/credits/balance \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://perceptron.cloud/api/credits/balance",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(response.json())
# {"balance": 2500, "currency": "USD_CENTS"}

POST /api/credits/purchase Client or Master Key

Submits an on-chain USDT transaction hash for verification and credits your account. The transaction must be a USDT transfer to the Perceptron receiving address (from GET /api/credits/receiving-address) on a supported chain. Minimum purchase: $5. Maximum transaction age: 5 minutes. Duplicate hashes are rejected.

Field Type Required Description
tx_hash string yes On-chain transaction hash (0x…)
token string yes Token symbol. Should be “USDT”
amount string yes Dollar amount as decimal string, e.g. “25.00”
chain_id uint64 yes EIP-155 chain ID of the network used, e.g. 8453 for Base. See Billing for all supported chain IDs
{
  "status": "ok",
  "amount_credited": 2500,
  "new_balance": 5000
}
Field Type Description
amount_credited int64 Cents added to your balance
new_balance int64 Balance after crediting, in cents
400“invalid amount”
400“minimum purchase is $5”
400“transaction verification failed (amount, token, or recipient mismatch)”
400“transaction expired: older than N seconds”
409“transaction already processed”
curl https://perceptron.cloud/api/credits/purchase \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tx_hash": "0xabc123...",
    "token": "USDT",
    "amount": "25.00",
    "chain_id": 8453
  }'
import requests, json

response = requests.post(
    "https://perceptron.cloud/api/credits/purchase",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={
        "tx_hash": "0xabc123...",
        "token": "USDT",
        "amount": "25.00",
        "chain_id": 8453,
    },
)
print(json.dumps(response.json(), indent=2))

GET /api/credits/history Client or Master Key

Returns the full credit transaction history for the authenticated wallet.

{
  "transactions": [
    {
      "id": "uuid",
      "user_address": "0xabc...",
      "tx_type": "Purchase",
      "amount": 2500,
      "balance_after": 5000,
      "description": "Purchased $25.00 credits with USDT (tx: 0xabc...)",
      "timestamp": 1748000000,
      "tx_hash": "0xabc123...",
      "status": "Completed"
    }
  ]
}
Field Type Description
id string Transaction UUID
tx_type string Purchase, ChatInference, SttInference, NewsFeed, or Domain
amount int64 Cents. Positive = credit added, negative = debit
balance_after int64 Balance after this transaction, in cents
timestamp uint64 Unix seconds
tx_hash string? On-chain hash. Only present on Purchase transactions
status string Pending, Completed, or Failed
curl https://perceptron.cloud/api/credits/history \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://perceptron.cloud/api/credits/history",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
for tx in response.json()["transactions"]:
    print(tx["tx_type"], tx["amount"], tx["status"])

GET /api/credits/receiving-address No Auth

Returns the USDT receiving address that you should send funds to when purchasing credits. This address is the same across all supported chains.

{
  "address": "0x5B97e908A7AF2EF5C2176ed45F79b2f74Fa680CC"
}
curl https://perceptron.cloud/api/credits/receiving-address
import requests

response = requests.get("https://perceptron.cloud/api/credits/receiving-address")
print(response.json()["address"])

Points

GET /api/points/balance Client or Master Key

Returns the authenticated wallet’s reward points balance. Points are non-monetary integers earned through platform activity.

{
  "points": 350
}
curl https://perceptron.cloud/api/points/balance \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://perceptron.cloud/api/points/balance",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(response.json()["points"])

GET /api/points/history Client or Master Key

Returns the points transaction history for the authenticated wallet.

{
  "transactions": [
    {
      "id": "uuid",
      "user_address": "0xabc...",
      "trigger_type": "welcome_bonus",
      "points_awarded": 250,
      "description": "Welcome bonus: first purchase of $25.00 credits",
      "timestamp": 1748000000,
      "metadata": {"purchase_amount_cents": 2500}
    }
  ]
}
Field Type Description
trigger_type string What caused the award. Known values: welcome_bonus, credit_purchase, chat_usage, feed_active, domain_active
points_awarded int64 Points granted in this transaction
metadata object? Optional trigger-specific context (e.g. purchase amount)
curl https://perceptron.cloud/api/points/history \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://perceptron.cloud/api/points/history",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
for tx in response.json()["transactions"]:
    print(tx["trigger_type"], tx["points_awarded"])

Domains

Reserve a subdomain under pct.site and expose a local port to the internet via an encrypted WebSocket tunnel. Billed from your credit balance.

POST /api/domains/reserve Client or Master Key

Reserves a subdomain under pct.site. Deducts the first hour’s cost immediately. Subdomains are first-come-first-served.

Field Type Required Description
subdomain string yes 2–32 characters. Lowercase alphanumeric and hyphens only. No leading or trailing hyphens. Cannot be a reserved word

Reserved words (rejected): www, api, mail, ftp, admin, tunnel, ns1, ns2, mx, smtp, imap, pop, app, dev, staging, test

{
  "id": "d1e2f3a4-...",
  "subdomain": "myapp",
  "full_domain": "myapp.pct.site",
  "hourly_cost_cents": 10,
  "prepaid_until": 1748003600
}
Field Type Description
id string Domain UUID. Use in all subsequent domain calls
full_domain string Public URL your tunnel will be reachable at
hourly_cost_cents int64 Cents billed per hour
prepaid_until uint64 Unix timestamp of next billing event
400“subdomain must be at least 2 characters”
402“insufficient credits for domain reservation”
402“no account found, purchase credits first”
409“myapp.pct.site is already reserved”
curl https://perceptron.cloud/api/domains/reserve \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"subdomain": "myapp"}'
import requests, json

response = requests.post(
    "https://perceptron.cloud/api/domains/reserve",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"subdomain": "myapp"},
)
domain = response.json()
print(domain["full_domain"], domain["id"])

GET /api/domains Client or Master Key

Lists all domains reserved by the authenticated wallet.

{
  "domains": [
    {
      "id": "d1e2f3a4-...",
      "subdomain": "myapp",
      "full_domain": "myapp.pct.site",
      "service_id": "domain-a1b2c3d4",
      "created_at": 1748000000,
      "local_port": 10001,
      "tunnel_active": false
    }
  ],
  "base_domain": "pct.site",
  "hourly_cost_cents": 10,
  "port_range": [10000, 10099]
}
Field Type Description
local_port uint16? Assigned local port (10000–10099). null if not yet assigned
tunnel_active bool Whether the WebSocket tunnel is currently connected
port_range array Valid port range: [10000, 10099]
curl https://perceptron.cloud/api/domains \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://perceptron.cloud/api/domains",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
for domain in response.json()["domains"]:
    print(domain["full_domain"], "active:", domain["tunnel_active"])

GET /api/domains/{id} Client or Master Key

Returns a single domain. You must own the domain.

Parameter Type Description
id string Domain UUID from POST /api/domains/reserve or GET /api/domains

Same object shape as items in GET /api/domains.

curl https://perceptron.cloud/api/domains/d1e2f3a4-... \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

domain_id = "d1e2f3a4-..."
response = requests.get(
    f"https://perceptron.cloud/api/domains/{domain_id}",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(response.json())

POST /api/domains/{id}/assign Client or Master Key

Assigns a local port to the domain. The tunnel will route public HTTPS traffic to localhost:{port}. Port must be in the range 10000–10099 and not already assigned to another domain of yours.

Parameter Type Description
id string Domain UUID
Field Type Required Description
port uint16 yes Local port traffic will be routed to. Must be 10000–10099 inclusive
{
  "status": "assigned",
  "port": 10001
}
400“port must be between 10000 and 10099”
409“port 10001 is already assigned to other.pct.site”
curl https://perceptron.cloud/api/domains/d1e2f3a4-.../assign \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"port": 10001}'
import requests

domain_id = "d1e2f3a4-..."
response = requests.post(
    f"https://perceptron.cloud/api/domains/{domain_id}/assign",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"port": 10001},
)
print(response.json())

POST /api/domains/{id}/unassign Client or Master Key

Clears the port assignment from the domain. No request body required.

Parameter Type Description
id string Domain UUID
{"status": "unassigned"}
curl -X POST https://perceptron.cloud/api/domains/d1e2f3a4-.../unassign \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

domain_id = "d1e2f3a4-..."
response = requests.post(
    f"https://perceptron.cloud/api/domains/{domain_id}/unassign",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(response.json())

POST /api/domains/{id}/tunnel-token Client or Master Key

Issues a short-lived JWT (5-minute TTL) for establishing a tunnel WebSocket connection to the domains server. A port must be assigned before calling this endpoint. The Perceptron TUI and frontend server use this automatically. You only need it if you are building a custom tunnel client.

Parameter Type Description
id string Domain UUID
{
  "tunnel_token": "eyJ...",
  "subdomain": "myapp",
  "expires_at": 1748000300,
  "local_port": 10001
}
Field Type Description
tunnel_token string JWT bearer token. Present to the domains WebSocket server at connection time
expires_at uint64 Unix timestamp. Token is valid for 5 minutes from issuance
local_port uint16 Local port traffic is routed to
400“assign a port before connecting the tunnel”
curl -X POST https://perceptron.cloud/api/domains/d1e2f3a4-.../tunnel-token \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

domain_id = "d1e2f3a4-..."
response = requests.post(
    f"https://perceptron.cloud/api/domains/{domain_id}/tunnel-token",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(response.json()["tunnel_token"])

POST /api/domains/release Client or Master Key

Releases a reserved domain. The subdomain becomes available for others to reserve immediately. No refund for remaining prepaid time. You must own the domain.

Field Type Required Description
domain_id string yes UUID of the domain to release
{"status": "released"}
403“you do not own this domain”
404“domain not found”
curl https://perceptron.cloud/api/domains/release \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain_id": "d1e2f3a4-..."}'
import requests

response = requests.post(
    "https://perceptron.cloud/api/domains/release",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"domain_id": "d1e2f3a4-..."},
)
print(response.json())

Frontend-only domain endpoints: the following three routes are only available on the local frontend server (http://localhost:9300) and are used by the TUI. They are not accessible on perceptron.cloud.

POST /api/domains/{id}/connect  ·  POST /api/domains/{id}/disconnect  ·  GET /api/domains/tunnels/status


News Feeds

Subscribe to real-time news streams. An active subscription is required to read items.

POST /api/feeds/news/subscribe Client or Master Key

Subscribes to the news feed. Deducts the first hour’s cost immediately. No request body required.

{
  "status": "subscribed",
  "prepaid_until": 1748003600,
  "hourly_cost_cents": 10
}
402“insufficient credits for news feed subscription”
402“no account found, purchase credits first”
409“already subscribed to news feed”
curl -X POST https://perceptron.cloud/api/feeds/news/subscribe \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.post(
    "https://perceptron.cloud/api/feeds/news/subscribe",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(response.json())

POST /api/feeds/news/unsubscribe Client or Master Key

Cancels the active news feed subscription. No refund for remaining prepaid time. No request body required.

{"status": "unsubscribed"}
curl -X POST https://perceptron.cloud/api/feeds/news/unsubscribe \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.post(
    "https://perceptron.cloud/api/feeds/news/unsubscribe",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(response.json())

GET /api/feeds/news Client or Master Key

Returns a paginated list of news items. An active subscription is required.

Parameter Type Default Description
limit uint 50 Maximum items to return. Capped at 100
category string Filter by category ID. See GET /api/feeds/news/categories for valid values
before uint64 Unix timestamp cursor. Returns only items older than this value. Use for pagination
{
  "items": [
    {
      "id": "tweet-1234567890",
      "timestamp": 1748000000,
      "category": "crypto",
      "headline": "Bitcoin surges past $100,000 as institutions buy",
      "summary": "Bitcoin reached a new all-time high driven by institutional demand and ETF inflows.",
      "source": "Reuters",
      "link": "https://reuters.com/..."
    }
  ]
}
Field Type Description
id string Item ID in the format tweet-{tweet_id}
timestamp uint64 Unix seconds of the original source post
category string One of the category IDs from GET /api/feeds/news/categories
headline string LLM-generated headline, max 15 words
summary string LLM-generated summary, max 40 words
source string Human-readable source name
link string? Original article URL, if available
403“news feed subscription required”
curl "https://perceptron.cloud/api/feeds/news?category=crypto&limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://perceptron.cloud/api/feeds/news",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    params={"category": "crypto", "limit": 20},
)
for item in response.json()["items"]:
    print(item["timestamp"], item["headline"])

GET /api/feeds/news/{id} Client or Master Key

Returns a single news item by ID. An active subscription is required.

Parameter Type Description
id string Item ID, e.g. tweet-1234567890

Single NewsItem object. Same shape as items in GET /api/feeds/news.

403“news feed subscription required”
404“not found”
curl https://perceptron.cloud/api/feeds/news/tweet-1234567890 \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://perceptron.cloud/api/feeds/news/tweet-1234567890",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(response.json())

GET /api/feeds/news/categories No Auth

Returns the list of available news categories. Use the id values to filter GET /api/feeds/news.

{
  "categories": [
    {"id": "crypto",        "name": "Crypto",        "description": "Cryptocurrency market news"},
    {"id": "politics",      "name": "Politics",       "description": "Political developments"},
    {"id": "economics",     "name": "Economics",      "description": "Economic indicators and analysis"},
    {"id": "technology",    "name": "Technology",     "description": "Technology and software news"},
    {"id": "regulatory",    "name": "Regulatory",     "description": "Financial regulation updates"},
    {"id": "entertainment", "name": "Entertainment",  "description": "Media and cultural trends"},
    {"id": "sports",        "name": "Sports",         "description": "Sports news and scores"}
  ]
}
curl https://perceptron.cloud/api/feeds/news/categories
import requests

response = requests.get("https://perceptron.cloud/api/feeds/news/categories")
for cat in response.json()["categories"]:
    print(cat["id"], "-", cat["description"])

GET /api/feeds/news/status Client or Master Key

Returns the current subscription status for the news feed.

{
  "feed_type": "news",
  "status": "Active",
  "subscribed_at": 1748000000,
  "prepaid_until": 1748003600,
  "hourly_cost": 10
}
Field Type Description
status string “Active” or “Inactive”
subscribed_at uint64? Unix seconds when subscription started. null if inactive
prepaid_until uint64? Unix seconds of next billing event. null if inactive
hourly_cost int64 Cents billed per hour
curl https://perceptron.cloud/api/feeds/news/status \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://perceptron.cloud/api/feeds/news/status",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(response.json())

API Keys

Client API keys (pct-...) are how you authenticate agents and scripts without exposing your master key. All key management endpoints require master key auth.

POST /api/keys/generate Master Key Only

Generates a new client API key. The full key is returned only once. It is never stored in plaintext and cannot be retrieved again. Accounts without a credit purchase are limited to 2 keys. Accounts with at least one completed credit purchase may hold up to 50 keys.

Field Type Required Description
label string yes Human-readable label for identifying this key. 1–64 characters
{
  "id": "uuid",
  "key": "pct-a1b2c3d4e5f6...",
  "key_prefix": "pct-a1b2",
  "label": "OpenClaw agent",
  "created_at": 1748000000
}
Field Type Description
key string Full secret key. Copy it now. This is the only time it is shown
key_prefix string First 8 characters for identification (e.g. pct-a1b2)
400“label must be between 1 and 64 characters”
403“maximum of 2 API keys reached without a credit purchase”
403“maximum of 50 API keys reached”
403“client API keys are not authorized for this operation”
curl https://perceptron.cloud/api/keys/generate \
  -H "Authorization: Bearer YOUR_MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label": "OpenClaw agent"}'
import requests, json

response = requests.post(
    "https://perceptron.cloud/api/keys/generate",
    headers={"Authorization": "Bearer YOUR_MASTER_KEY"},
    json={"label": "OpenClaw agent"},
)
data = response.json()
print("Key (save this):", data["key"])
print("Prefix:", data["key_prefix"])

GET /api/keys Master Key Only

Lists all client API keys for the authenticated wallet. Full key values are never returned, only prefixes and metadata.

{
  "keys": [
    {
      "id": "uuid",
      "key_prefix": "pct-a1b2",
      "label": "OpenClaw agent",
      "created_at": 1748000000,
      "last_used_at": 1748001000
    }
  ]
}
curl https://perceptron.cloud/api/keys \
  -H "Authorization: Bearer YOUR_MASTER_KEY"
import requests

response = requests.get(
    "https://perceptron.cloud/api/keys",
    headers={"Authorization": "Bearer YOUR_MASTER_KEY"},
)
for key in response.json()["keys"]:
    print(key["key_prefix"], key["label"], key["last_used_at"])

DELETE /api/keys/{id} Master Key Only

Revokes a client API key. The key becomes invalid immediately. You must own the key.

Parameter Type Description
id string Key UUID from GET /api/keys
{"status": "revoked"}
404“API key not found”
curl -X DELETE https://perceptron.cloud/api/keys/uuid-of-key \
  -H "Authorization: Bearer YOUR_MASTER_KEY"
import requests

key_id = "uuid-of-key"
response = requests.delete(
    f"https://perceptron.cloud/api/keys/{key_id}",
    headers={"Authorization": "Bearer YOUR_MASTER_KEY"},
)
print(response.json())

Metrics

Usage and cost metrics for your account. All metrics endpoints require master key auth. Responses support conditional requests via ETag / If-None-Match (returns 304 Not Modified when unchanged).

GET /api/metrics/summary Master Key Only

Returns a snapshot of current account usage: balance, burn rate, runway, 24-hour totals, and per-service cost breakdown.

{
  "credits_balance_cents": 5000,
  "burn_rate_cents_per_hour": 15,
  "runway_hours": 333.33,
  "total_tokens_24h": 50000,
  "total_requests_24h": 12,
  "total_cost_24h_cents": 120,
  "active_services": {
    "domains": 1,
    "feeds": 1,
    "tunnels": 0
  },
  "cost_by_service": [
    {"service": "chat",   "cost_cents": 90, "tokens": 50000},
    {"service": "stt",    "cost_cents": 10, "tokens": 0},
    {"service": "feed",   "cost_cents": 10, "tokens": 0},
    {"service": "domain", "cost_cents": 10, "tokens": 0}
  ]
}
Field Type Description
credits_balance_cents int64 Current balance in cents
burn_rate_cents_per_hour int64 Estimated hourly spend across all active services
runway_hours float Hours until balance reaches zero at current burn rate. 0.0 if no active spend
total_tokens_24h int64 Chat input + output tokens consumed in the last 24 hours
total_requests_24h uint64 Chat + STT requests in the last 24 hours
cost_by_service[].service string “chat”, “stt”, “feed”, or “domain”
cost_by_service[].tokens int64 Non-zero only for “chat”
curl https://perceptron.cloud/api/metrics/summary \
  -H "Authorization: Bearer YOUR_MASTER_KEY"
import requests, json

response = requests.get(
    "https://perceptron.cloud/api/metrics/summary",
    headers={"Authorization": "Bearer YOUR_MASTER_KEY"},
)
data = response.json()
print(f"Balance: ${data['credits_balance_cents'] / 100:.2f}")
print(f"Runway:  {data['runway_hours']:.1f} hours")

GET /api/metrics/history Master Key Only

Returns time-bucketed usage history. Use period and bucket_minutes together to control the window and granularity. Common combinations: period=1&bucket_minutes=5 (last hour, 5-min buckets) and period=24&bucket_minutes=120 (last 24 hours, 2-hour buckets).

Parameter Type Default Range Description
period uint64 24 1–720 Time window in hours
bucket_minutes uint64 60 1–1440 Width of each time bucket in minutes
{
  "period_hours": 24,
  "buckets": [
    {
      "timestamp": 1747913600,
      "chat_tokens": 5000,
      "chat_cost_cents": 12,
      "stt_cost_cents": 3,
      "feed_cost_cents": 2,
      "domain_cost_cents": 2,
      "requests": 3
    }
  ]
}
Field Type Description
timestamp uint64 Bucket start time as Unix seconds, aligned to the bucket boundary
chat_tokens int64 Input + output tokens from chat requests in this bucket
requests uint64 Chat + STT request count in this bucket
curl "https://perceptron.cloud/api/metrics/history?period=24&bucket_minutes=120" \
  -H "Authorization: Bearer YOUR_MASTER_KEY"
import requests, json

response = requests.get(
    "https://perceptron.cloud/api/metrics/history",
    headers={"Authorization": "Bearer YOUR_MASTER_KEY"},
    params={"period": 24, "bucket_minutes": 120},
)
data = response.json()
for bucket in data["buckets"]:
    print(bucket["timestamp"], "tokens:", bucket["chat_tokens"])

GET /api/metrics/keys Master Key Only

Returns usage metadata for all client API keys. Last used timestamps and labels are useful for auditing which keys are active.

{
  "keys": [
    {
      "key_prefix": "pct-a1b2",
      "label": "OpenClaw agent",
      "created_at": 1748000000,
      "last_used_at": 1748001000
    }
  ]
}
curl https://perceptron.cloud/api/metrics/keys \
  -H "Authorization: Bearer YOUR_MASTER_KEY"
import requests

response = requests.get(
    "https://perceptron.cloud/api/metrics/keys",
    headers={"Authorization": "Bearer YOUR_MASTER_KEY"},
)
for key in response.json()["keys"]:
    print(key["key_prefix"], "last used:", key["last_used_at"])

Wallet

GET /api/wallet/addresses Client or Master Key

Returns the EVM wallet address associated with the authenticated token.

{
  "address": "0xabcdef1234567890..."
}
curl https://perceptron.cloud/api/wallet/addresses \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://perceptron.cloud/api/wallet/addresses",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(response.json()["address"])

Personas

GET /api/personas/personalities No Auth

List all available personality templates. Safe to call without authentication.

curl https://perceptron.cloud/api/personas/personalities

GET /api/personas Client Key

List all personas owned by the authenticated user.

curl https://perceptron.cloud/api/personas \
  -H "Authorization: Bearer YOUR_API_KEY"

POST /api/personas/new Client Key

Create a new persona from a personality template.

{ "personality_id": "jensen", "name": "My Jensen" }
curl -X POST https://perceptron.cloud/api/personas/new \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"personality_id":"jensen","name":"My Jensen"}'

GET /api/personas/{id} Client Key

Get details for a single persona. Returns 403 if the persona belongs to a different address.

curl https://perceptron.cloud/api/personas/{id} \
  -H "Authorization: Bearer YOUR_API_KEY"

DELETE /api/personas/{id} Client Key

Hard delete a persona. Cascade: all sessions and workspace files are permanently removed.

curl -X DELETE https://perceptron.cloud/api/personas/{id} \
  -H "Authorization: Bearer YOUR_API_KEY"

GET /api/personas/{id}/sessions Client Key

List all sessions for a persona.

curl https://perceptron.cloud/api/personas/{id}/sessions \
  -H "Authorization: Bearer YOUR_API_KEY"

POST /api/personas/{id}/sessions Client Key

Create a new conversation session for a persona.

{ "name": "Evening chats" }
curl -X POST https://perceptron.cloud/api/personas/{id}/sessions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Evening chats"}'

DELETE /api/personas/{id}/sessions/{sid} Client Key

Delete a session and its message history.

curl -X DELETE https://perceptron.cloud/api/personas/{id}/sessions/{sid} \
  -H "Authorization: Bearer YOUR_API_KEY"

GET /api/personas/{id}/sessions/{sid}/messages Client Key

Paginated message history for a session in chronological order.

Name Required Description
limit No Max messages to return (default: 50)
before No Return messages before this message ID (cursor pagination)
curl "https://perceptron.cloud/api/personas/{id}/sessions/{sid}/messages?limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY"

POST /api/personas/{id}/sessions/{sid}/messages/new Client Key

Send a message to the persona. Returns 202 Accepted immediately. Poll messages or use the WebSocket stream to receive the response.

{ "content": "Hello" }
{ "message_id": "uuid" }
curl -X POST https://perceptron.cloud/api/personas/{id}/sessions/{sid}/messages/new \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content":"Hello, tell me about yourself."}'

WS /api/personas/{id}/sessions/{sid}/stream Client Key (via ?token=)

WebSocket connection for real-time message delivery. Authenticate via ?token=YOUR_API_KEY query parameter.

{ "type": "message", "content": "What are you thinking about?" }
{ "type": "message", "payload": { "message": { "id": "uuid", "role": "assistant", "content": "...", "created_at": 1716000010 } } }
# npm install -g wscat
wscat -c "wss://perceptron.cloud/api/personas/{id}/sessions/{sid}/stream?token=YOUR_API_KEY"
> {"type":"message","content":"Hello!"}