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: monetary amounts are
dollar strings (e.g. "25" = $25.00). 1
credit = $1.
Health
GET /api/health No Auth
Returns server status and current version. Useful for liveness checks.
{
"status": "ok",
"version": "3.1.2"
}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": "3.1.2",
"release_notes": "## 3.1.2\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/3.1.2/linux/perceptron.tar.gz"
],
"published_at": 1748000000
}| Field | Type | Description |
|---|---|---|
| version | string |
Semver version string (e.g. “3.1.2”)
|
| 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. 3.1.2
|
Same shape as GET /api/version/latest.
curl https://perceptron.cloud/api/version/3.1.2import requests
response = requests.get("https://perceptron.cloud/api/version/3.1.2")
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
}
}The usage object is constructed by Perceptron from
scratch; no upstream cost or provider metadata leaks through. When the
upstream provider reports prompt-cache activity,
usage.prompt_tokens_details is included:
"usage": {
"prompt_tokens": 10339,
"completion_tokens": 60,
"total_tokens": 10399,
"prompt_tokens_details": {
"cached_tokens": 10318,
"cache_write_tokens": 0
}
}cached_tokens are billed at the model’s
cache_read_per_1m rate (typically 0.1× input);
cache_write_tokens at cache_write_per_1m
(typically 1.25× input). When either rate is absent from
GET /api/models, the full input_per_1m rate
applies to all prompt tokens.
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,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0}}}
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_price_per_1m": "x",
"output_price_per_1m": "x"
}
}
]
}| 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_price_per_1m | string | Cost per 1 million input tokens |
| pricing.output_price_per_1m | string | Cost 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_price_per_hour": "x" }
}
]
}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_price_per_1m_chars": "x" },
"context_length": 4000
}
]
}Video
Video generation is asynchronous: submit a job, poll its status, download the result. Billed per output second at the model’s rate for the requested resolution; the full quoted cost is reserved at submit and refunded on failure. Finished videos are stored as artifacts and accrue hourly artifact storage charges until deleted.
Submit Video Job
POST /api/v1/videos Client or Master Key
Submit a video generation job. Returns 202 Accepted with
the job id and polling URL.
| Field | Type | Required | Description |
|---|---|---|---|
| model | string | yes |
Video model ID from GET /api/v1/videos/models
|
| prompt | string | yes | Text description of the video to generate |
| duration | integer | yes | Clip duration in seconds (per-model min/max) |
| resolution | string | no |
Output resolution tier (e.g. 2K); defaults to the model’s
first supported tier
|
| aspect_ratio | string | no |
Aspect ratio (e.g. 16:9, 9:16,
1:1)
|
| size | string | no |
Exact pixel dimensions in WIDTHxHEIGHT format
|
| generate_audio | boolean | no | Generate audio alongside the video (models with audio support) |
| seed | integer | no | Seed for deterministic generation (not guaranteed) |
| frame_images | array | no |
First/last frame images for image-to-video (frame_type:
first_frame / last_frame)
|
| input_references | array | no | Reference images for style guidance (reference-to-video) |
callback_url and provider are not supported
and are rejected with 400.
{
"id": "0e1b0c3f-...",
"polling_url": "/api/v1/videos/0e1b0c3f-...",
"status": "pending"
}curl -X POST https://perceptron.cloud/api/v1/videos \
-H "Authorization: Bearer $PERCEPTRON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "minimax/hailuo-3", "prompt": "A golden retriever playing fetch on a sunny beach", "duration": 5, "resolution": "2K", "aspect_ratio": "16:9"}'Poll Video Job
GET /api/v1/videos/{id} Client or Master Key
Poll a job’s status. Statuses: pending,
in_progress, completed, failed,
cancelled, expired. Terminal failure statuses
are fully refunded. Completed jobs include unsigned_urls
and usage.cost (USD).
{
"id": "0e1b0c3f-...",
"polling_url": "/api/v1/videos/0e1b0c3f-...",
"status": "completed",
"unsigned_urls": ["/api/v1/videos/0e1b0c3f-.../content?index=0"],
"usage": { "cost": 1.5 }
}curl https://perceptron.cloud/api/v1/videos/0e1b0c3f-... \
-H "Authorization: Bearer $PERCEPTRON_API_KEY"Download Video Content
GET /api/v1/videos/{id}/content Client or Master Key
Download the generated video. Raw video bytes with
Content-Type: video/mp4, served from the locally stored
artifact. Accepts an optional index query parameter
(default 0).
curl https://perceptron.cloud/api/v1/videos/0e1b0c3f-.../content \
-H "Authorization: Bearer $PERCEPTRON_API_KEY" \
--output video.mp4List Video Jobs
GET /api/v1/videos Client or Master Key
List the authenticated user’s video generation jobs, newest first.
{
"jobs": [
{
"id": "0e1b0c3f-...",
"polling_url": "/api/v1/videos/0e1b0c3f-...",
"status": "completed",
"model": "minimax/hailuo-3",
"prompt": "A golden retriever playing fetch on a sunny beach",
"duration": 5,
"resolution": "2K",
"aspect_ratio": "16:9",
"generate_audio": null,
"created_at": 1785000000,
"completed_at": 1785000180,
"unsigned_urls": ["/api/v1/videos/0e1b0c3f-.../content?index=0"],
"usage": { "cost": 1.5 },
"artifact_id": "b7f2..."
}
]
}Delete Video Job
DELETE /api/v1/videos/{id} Client or Master Key
Delete a video generation job and its stored artifact, stopping artifact storage billing for those bytes.
curl -X DELETE https://perceptron.cloud/api/v1/videos/0e1b0c3f-... \
-H "Authorization: Bearer $PERCEPTRON_API_KEY"List Video Models
GET /api/v1/videos/models No Auth
Returns all video generation models with their capabilities and per-second pricing per resolution tier.
{
"object": "list",
"data": [
{
"id": "minimax/hailuo-3",
"name": "Hailuo 3",
"object": "model",
"owned_by": "perceptron",
"modalities": ["video"],
"supported_resolutions": ["2K"],
"supported_aspect_ratios": ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"],
"min_duration": 4,
"max_duration": 15,
"supports_audio": true,
"pricing_skus": { "2K": "x" }
}
]
}Image
Image generation is asynchronous (submit → poll → download). Billed
per output image at the model’s rate for the requested resolution; the
full quoted cost (n × rate) is reserved at submit and
refunded on failure. Finished images are stored as artifacts and accrue
hourly artifact storage charges until deleted. See Billing.
Submit Image Job
POST /api/v1/images Client or Master Key
Submit an image generation job. Returns 202 with the job
id and a polling URL.
curl -X POST https://perceptron.cloud/api/v1/images \
-H "Authorization: Bearer $PERCEPTRON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "krea/krea-2-large",
"prompt": "A neon-lit cyberpunk street market in the rain",
"resolution": "1K",
"aspect_ratio": "16:9",
"n": 1,
"seed": 42
}'| Field | Type | Required | Description |
|---|---|---|---|
model |
string | yes | Image model ID |
prompt |
string | yes | Text description of the image to generate |
resolution |
string | no | Output resolution tier; defaults to the model’s first supported tier |
aspect_ratio |
string | no | Aspect ratio (e.g. 16:9, 9:16,
1:1) |
n |
integer | no | Number of images to generate (1..max_n, default
1) |
seed |
integer | no | Seed for deterministic generation (models with seed support) |
quality |
string | no | auto, low, medium, or
high |
output_format |
string | no | png, jpeg, or webp |
background |
string | no | auto, transparent, or
opaque |
input_references |
array | no | Reference images for image-to-image generation (up to 1) |
output_compression, stream,
size, callback_url, and provider
are not supported; rejected with 400. Insufficient credits
returns 402.
Response (202)
{
"id": "0e1b0c3f-...",
"polling_url": "/api/v1/images/0e1b0c3f-...",
"status": "pending"
}Example
curl -X POST https://perceptron.cloud/api/v1/images \
-H "Authorization: Bearer $PERCEPTRON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"krea/krea-2-large","prompt":"a red panda astronaut","resolution":"1K"}'Poll Image Job
GET /api/v1/images/{id} Client or Master Key
Poll an image generation job’s status. Statuses:
pending, in_progress, completed,
failed, cancelled. Completed jobs include
unsigned_urls (one per n),
artifact_ids, and usage.cost (USD).
{
"id": "0e1b0c3f-...",
"status": "completed",
"model": "krea/krea-2-large",
"prompt": "a red panda astronaut",
"n": 1,
"unsigned_urls": ["/api/v1/images/0e1b0c3f-.../content?index=0"],
"usage": { "cost": 0.30 }
}curl https://perceptron.cloud/api/v1/images/0e1b0c3f-... \
-H "Authorization: Bearer $PERCEPTRON_API_KEY"Download Image Content
GET /api/v1/images/{id}/content?index=N Client or Master Key
Download one generated image (raw bytes). index is
0-based (use 0..n-1).
Content-Type reflects the output format
(e.g. image/png).
curl https://perceptron.cloud/api/v1/images/0e1b0c3f-.../content?index=0 \
-H "Authorization: Bearer $PERCEPTRON_API_KEY" --output image.pngList Image Jobs
GET /api/v1/images Client or Master Key
List the caller’s image generation jobs, newest first.
{
"jobs": [
{
"id": "0e1b0c3f-...",
"status": "completed",
"model": "krea/krea-2-large",
"prompt": "a red panda astronaut",
"n": 1,
"usage": { "cost": 0.30 }
}
]
}curl https://perceptron.cloud/api/v1/images \
-H "Authorization: Bearer $PERCEPTRON_API_KEY"Delete Image Job
DELETE /api/v1/images/{id} Client or Master Key
Delete an image generation job and its stored artifacts, stopping artifact storage billing for those bytes.
curl -X DELETE https://perceptron.cloud/api/v1/images/0e1b0c3f-... \
-H "Authorization: Bearer $PERCEPTRON_API_KEY"List Image Models
GET /api/v1/images/models No Auth
Returns all image generation models with their capabilities and per-image pricing per resolution tier.
{
"object": "list",
"data": [
{
"id": "krea/krea-2-large",
"name": "Krea 2 Large",
"object": "model",
"owned_by": "perceptron",
"input_modalities": ["text", "image"],
"output_modalities": ["image"],
"supported_resolutions": ["1K"],
"supported_aspect_ratios": ["1:1", "4:3", "3:2", "16:9", "4:5", "2:3", "9:16"],
"supports_seed": true,
"max_n": 10,
"pricing_skus": { "1K": "x" }
}
]
}Vision
Video (and optionally image) understanding with text output —
descriptions, analysis, or Q&A. Billed per token (input + output) at
the model’s per-million rates, the same as chat. Recorded as
VisionInference transactions. See Billing.
Analyze Video/Image
POST /api/v1/vision Client or Master Key
Analyze a video or image with a text prompt and return text output.
multipart/form-data upload. Supported video formats: mp4,
webm, mov, mkv.
| Part | Type | Required | Description |
|---|---|---|---|
| file | file | no | The video or image file (mp4, webm, mov, mkv, png, jpg, gif, webp). At least one media input is required. |
| image_file | file | no | Additional image (repeatable for multiple images) |
| prompt | string | yes | Text prompt (description, question, or instruction) |
| model | string | no |
Vision model ID from GET /api/v1/vision/models
|
| artifact_id | string | no |
Artifact reference (existing artifact id, alternative to
file)
|
| image_artifact_id | string | no |
Image artifact reference (repeatable for multiple; alternative to
image_file)
|
| max_tokens | uint64 | no | Maximum output tokens |
{
"text": "A dog runs across a sunny beach, chasing a thrown ball.",
"usage": {
"prompt_tokens": 24,
"completion_tokens": 16,
"total_tokens": 40
}
}curl https://perceptron.cloud/api/v1/vision \
-H "Authorization: Bearer $PERCEPTRON_API_KEY" \
-F "model=minimax/m3" \
-F "prompt=Describe what is happening in this video." \
-F "file=@clip.mp4"List Vision Models
GET /api/v1/vision/models No Auth
Returns all vision models — those with "video" in their
input_modalities — with their per-million token
pricing.
{
"object": "list",
"data": [
{
"id": "minimax/m3",
"name": "MiniMax M3",
"object": "model",
"owned_by": "perceptron",
"input_modalities": ["video", "image"],
"pricing": {
"input_price_per_1m": "x",
"output_price_per_1m": "x"
}
}
]
}curl https://perceptron.cloud/api/v1/vision/modelsCredits
GET /api/credits/balance Client or Master Key
Returns the current credit balance as a dollar string.
{
"balance": "25",
"currency": "USD"
}| Field | Type | Description |
|---|---|---|
| balance | string |
Current balance. “25” = $25.00
|
| currency | string |
Always “USD”
|
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": 250000000, "currency": "USD"}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": "2.5",
"new_balance": "5"
}| Field | Type | Description |
|---|---|---|
| amount_credited | string | Added to your balance |
| new_balance | string | Balance after crediting |
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": "25",
"balance_after": "50",
"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, TtsInference,
PersonaInference, WebSearch,
NewsFeed, Domain, Storage,
VideoInference, ImageInference,
VisionInference, AnimationRender,
SpecialAward, or CreditAward
|
| amount | string | Positive = credit added, negative = debit |
| balance_after | string | Balance after this transaction |
| 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": 2500000000}
}
]
}| 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": "x",
"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 | string | 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": "x",
"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 frontend uses 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": "x"
}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": "x"
}| 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 | string | 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": "50",
"burn_rate_per_hour": "0.15",
"runway_hours": 333.33,
"total_tokens_24h": 50000,
"total_requests_24h": 12,
"total_cost_24h": "1.2",
"active_services": {
"domains": 1,
"feeds": 1,
"tunnels": 0
},
"cost_by_service": [
{"service": "chat", "cost": "0.9", "tokens": 50000},
{"service": "stt", "cost": "0.1", "tokens": 0},
{"service": "feed", "cost": "0.014", "tokens": 0},
{"service": "domain", "cost": "0.014", "tokens": 0},
{"service": "web_search", "cost": "0.05", "tokens": 0}
]
}| Field | Type | Description |
|---|---|---|
| credits_balance | string | Current balance |
| burn_rate_per_hour | string | 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 |
| total_cost_24h | string | Total spend in the last 24 hours |
| cost_by_service[].service | string |
“chat”, “stt”, “tts”,
“feed”, “domain”, “storage”,
“web_search”, “video”, “image”,
“vision”, or “animation”
|
| cost_by_service[].cost | string | 24-hour spend for this service |
| 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'] / 100_000_000:.8f}")
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": 12000000,
"stt_cost": 3000000,
"feed_cost": 2000000,
"domain_cost": 2000000,
"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
Create and manage Personas through the Perceptron app or the
Perceptron Telegram bot. You can list the personas you own via the API
(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"