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/health
import requests

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

Version

Release information endpoints. No authentication required.

GET /api/version/latest No Auth

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

{
  "version": "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
404“no releases found”
curl https://perceptron.cloud/api/version/latest
import requests

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

GET /api/version/{version} No Auth

Returns a specific release record by semver string.

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

Same shape as GET /api/version/latest.

404“release ‘3.1.2’ not found”
curl https://perceptron.cloud/api/version/3.1.2
import 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/challenge
import requests

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

POST /api/auth/verify No Auth

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

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

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

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

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

Chat Completions

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

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

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

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

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

GET /api/models No Auth

Returns all active models with pricing and capability metadata.

{
  "object": "list",
  "data": [
    {
      "id": "moonshotai/kimi-k2.6",
      "name": "Kimi K2.6",
      "object": "model",
      "created": 1748000000,
      "owned_by": "perceptron",
      "context_length": 262144,
      "max_output_tokens": 183500,
      "frontier_rank": 7,
      "pricing": {
        "input_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
Pricing is configurable; query the live endpoint for current rates. Values shown in examples are illustrative.
curl https://perceptron.cloud/api/models
import requests

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

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.mp3

List 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.mp4

List 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.png

List 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"
      }
    }
  ]
}
Pricing is configurable; query the live endpoint for current rates. Values shown in examples are illustrative.
curl https://perceptron.cloud/api/v1/vision/models

Credits

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
400“invalid amount”
400“minimum purchase is $5”
400“transaction verification failed (amount, token, or recipient mismatch)”
400“transaction expired: older than N seconds”
409“transaction already processed”
curl https://perceptron.cloud/api/credits/purchase \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tx_hash": "0xabc123...",
    "token": "USDT",
    "amount": "25.00",
    "chain_id": 8453
  }'
import requests, json

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

GET /api/credits/history Client or Master Key

Returns the full credit transaction history for the authenticated wallet.

{
  "transactions": [
    {
      "id": "uuid",
      "user_address": "0xabc...",
      "tx_type": "Purchase",
      "amount": "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-address
import requests

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

Points

GET /api/points/balance Client or Master Key

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

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

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

GET /api/points/history Client or Master Key

Returns the points transaction history for the authenticated wallet.

{
  "transactions": [
    {
      "id": "uuid",
      "user_address": "0xabc...",
      "trigger_type": "welcome_bonus",
      "points_awarded": 250,
      "description": "Welcome bonus: first purchase of $25.00 credits",
      "timestamp": 1748000000,
       "metadata": {"purchase_amount": 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
400“subdomain must be at least 2 characters”
402“insufficient credits for domain reservation”
402“no account found, purchase credits first”
409“myapp.pct.site is already reserved”
curl https://perceptron.cloud/api/domains/reserve \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"subdomain": "myapp"}'
import requests, json

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

GET /api/domains Client or Master Key

Lists all domains reserved by the authenticated wallet.

{
  "domains": [
    {
      "id": "d1e2f3a4-...",
      "subdomain": "myapp",
      "full_domain": "myapp.pct.site",
      "service_id": "domain-a1b2c3d4",
      "created_at": 1748000000,
      "local_port": 10001,
      "tunnel_active": false
    }
  ],
  "base_domain": "pct.site",
  "hourly_cost": "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
}
400“port must be between 10000 and 10099”
409“port 10001 is already assigned to other.pct.site”
curl https://perceptron.cloud/api/domains/d1e2f3a4-.../assign \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"port": 10001}'
import requests

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

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

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

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

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

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

Issues a short-lived JWT (5-minute TTL) for establishing a tunnel WebSocket connection to the domains server. A port must be assigned before calling this endpoint. The Perceptron 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
400“assign a port before connecting the tunnel”
curl -X POST https://perceptron.cloud/api/domains/d1e2f3a4-.../tunnel-token \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

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

POST /api/domains/release Client or Master Key

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

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

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

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

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


News Feeds

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

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

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

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

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

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

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

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

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

GET /api/feeds/news Client or Master Key

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

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

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

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

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

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

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

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

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

GET /api/feeds/news/categories No Auth

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

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

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

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

Returns the current subscription status for the news feed.

{
  "feed_type": "news",
  "status": "Active",
  "subscribed_at": 1748000000,
  "prepaid_until": 1748003600,
  "hourly_cost": "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())

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
400“query is required and must be non-empty”
402“insufficient credits for web search”
502“web search unavailable”
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)
400“label must be between 1 and 64 characters”
403“maximum of 2 API keys reached without a credit purchase”
403“maximum of 50 API keys reached”
403“client API keys are not authorized for this operation”
curl https://perceptron.cloud/api/keys/generate \
  -H "Authorization: Bearer YOUR_MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label": "OpenClaw agent"}'
import requests, json

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

GET /api/keys Master Key Only

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

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

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

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

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

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

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

Metrics

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

GET /api/metrics/summary Master Key Only

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

{
  "credits_balance": "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"