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/healthimport 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 |
curl https://perceptron.cloud/api/version/latestimport 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.
curl https://perceptron.cloud/api/version/1.2.6import 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/challengeimport 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 |
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]
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/modelsimport 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 |
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-addressimport 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 |
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
}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 |
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"}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
}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 |
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.
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/categoriesimport 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)
|
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"}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/personalitiesGET /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!"}