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"])Speech
Transcribe Audio (Speech-to-Text)
POST /api/v1/audio/transcriptions · /api/stt/transcriptions Client or Master Key
Transcribe an audio file to text. OpenAI-compatible
multipart/form-data upload. Billed per minute of audio.
Maximum 50 MB per file.
| Part | Type | Required | Description |
|---|---|---|---|
| file | file | yes | The audio file (mp3, wav, ogg, m4a, flac, webm, etc.) |
| model | string | no |
STT model ID from GET /api/stt/models
|
| language | string | no |
ISO-639-1 language hint (e.g. en)
|
| response_format | string | no |
Response format (default verbose_json)
|
{
"text": "Hello, this is the transcription.",
"duration": 3.2
}curl https://perceptron.cloud/api/v1/audio/transcriptions \
-H "Authorization: Bearer $PERCEPTRON_API_KEY" \
-F "model=openai/whisper-large-v3" \
-F "file=@speech.ogg"List STT Models
GET /api/stt/models No Auth
Returns all speech-to-text models with their per-hour audio pricing.
{
"object": "list",
"data": [
{
"id": "openai/whisper-large-v3",
"name": "Whisper Large v3",
"object": "model",
"owned_by": "perceptron",
"pricing": { "audio_cents_per_hour": 18 }
}
]
}Synthesize Speech (Text-to-Speech)
POST /api/v1/audio/speech · /api/tts/speech Client or Master Key
Synthesize speech from text. Returns raw audio bytes. Billed per input character.
| Field | Type | Required | Description |
|---|---|---|---|
| model | string | yes |
TTS model ID from GET /api/tts/models
|
| input | string | yes | The text to synthesize |
| voice | string | no |
Voice id (provider-specific, e.g. af_heart)
|
| response_format | string | no |
Audio format: mp3 (default), wav,
opus, ogg, flac
|
Raw audio bytes with Content-Type: audio/mpeg (or
audio/wav, audio/ogg per
response_format).
curl https://perceptron.cloud/api/v1/audio/speech \
-H "Authorization: Bearer $PERCEPTRON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "hexgrad/kokoro-82m", "input": "Hello, world.", "voice": "af_heart", "response_format": "mp3"}' \
--output speech.mp3List TTS Models
GET /api/tts/models No Auth
Returns all text-to-speech models with their per-character pricing.
{
"object": "list",
"data": [
{
"id": "hexgrad/kokoro-82m",
"name": "Kokoro 82M",
"object": "model",
"owned_by": "perceptron",
"pricing": { "tts_cents_per_1m_chars": 124 },
"context_length": 4000
}
]
}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, PersonaInference,
PersonaStorage, NewsFeed, Domain,
or WebSearch
|
| 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())Web Search
Query the live web for current information. Billed per request.
POST /api/web_search Client or Master Key
Searches the web and returns structured results. Credits are reserved before the search executes; on failure the reservation is refunded.
| Field | Type | Required | Description |
|---|---|---|---|
| query | string | yes | The search query |
| count | integer | no | Number of results (1-20, default 8) |
{
"query": "latest ethereum gas prices",
"results": [
{
"title": "Ethereum Gas Tracker",
"url": "https://example.com/gas",
"snippet": "Current gas price: 12 gwei..."
}
]
}| Field | Type | Description |
|---|---|---|
| query | string | The original query string |
| results | array | Search result objects |
| results[].title | string | Result title |
| results[].url | string | Result URL |
| results[].snippet | string | Result snippet/description |
curl https://perceptron.cloud/api/web_search \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "latest ethereum gas prices", "count": 5}'import requests, json
response = requests.post(
"https://perceptron.cloud/api/web_search",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"query": "latest ethereum gas prices", "count": 5},
)
for result in response.json()["results"]:
print(result["title"], result["url"])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},
{"service": "web_search", "cost_cents": 5, "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”,
“domain”, “persona_memory”, or
“web_search”
|
| 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
Persona management via the API is currently discontinued. Personas are created and managed through the Perceptron app and the Perceptron Telegram bot. The only public persona endpoint is listing the personas you own (
GET /api/personas, below).
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"