Docs
Submit verified-inference jobs in one Python call. Get back numpy embeddings and a tamper-proof receipt that proves the result is correct. Powered by the cyberian-client package.
Overview
You name a model, send your inputs, get back outputs and a tamper-proof receipt. The platform handles execution, independent verification, and the cryptographic attestations that make the result auditable.
- One call:
submit_and_wait(model=…, inputs=[…])returns a numpy array and a receipt. - Typed exceptions: distinguish auth, quota, rate-limit, verification failures.
- Numpy outputs: embeddings come back as
np.ndarray(shape=(N, D), dtype=float32). - Auditable receipts: every result is anchored in Merkle roots that you can validate offline.
Install
Python 3.9+ required.
pip install cyberian-client
View on PyPI: pypi.org/project/cyberian-client and see version history, package hashes, release feed.
Authenticate
Get a key from signup. Pass it to the client constructor or set CYBERIAN_API_KEY in your environment.
from cyberian import Client
client = Client(api_key="cyb_yourlabel_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
# or: client = Client() # reads CYBERIAN_API_KEY from env
Submit your first job
The simplest call is submit_and_wait - it submits, polls until settled, then fetches the receipt and the output array.
from cyberian import Client
client = Client()
texts = [
"The quick brown fox jumps over the lazy dog.",
"Cyberian Systems verifies AI inference end-to-end.",
"Merkle receipts make outputs auditable.",
]
result = client.submit_and_wait(
model="bge-large-v1.5",
inputs=texts,
)
print("Job:", result.job["id"])
print("Receipt:", result.receipt["receipt_id"])
print("Output shape:", result.embeddings.shape) # (3, 1024)
print("Output dtype:", result.embeddings.dtype) # float32
Step-by-step (mirrors the REST chain)
The same five calls the REST Typical flow walks through, expressed as SDK methods. Use this when you want explicit control over each step (e.g. inspecting the unsettled job, separating the verification pass for an audit log).
# 1. submit a job
job = client.submit_job(model="bge-large-v1.5", inputs=texts)
print(job["id"], job["status"]) # EXECUTING
# 2. block until terminal (SETTLED / FAILED / REFUNDED)
settled = client.wait_for_completion(job["id"], timeout_sec=600)
# 3. fetch the slim Merkle receipt
receipt = client.get_receipt(settled["receipt_id"])
# 4. server-side verification pass — recomputes every hash
check = client.verify_receipt(settled["receipt_id"])
assert check["valid"], check["errors"]
# 5. pull the embeddings (reshapes via response headers for you)
embeddings = client.get_job_output(settled["id"]) # np.ndarray(N, D)
Models
Every job names a model (model="..."). To list the models enabled for your account:
info = client.list_models()
print(info["models"])
The platform is model-agnostic. Need a model that isn't enabled for you? Email support@cyberiansystems.ai with the model reference (e.g. a Hugging Face URL). Onboarding a new model is a configuration step on our side - no code change on yours.
Verifying a receipt
Every settled job produces a tamper-proof receipt that anchors the inputs, the outputs, and the verification attestation in cryptographic Merkle roots. You can re-check it offline:
receipt = client.get_receipt(result.receipt["receipt_id"])
verified = client.verify_receipt(receipt["receipt_id"])
assert verified["valid"] is True
print(verified["checks"]) # every check should be True
If any byte of the receipt is mutated, the verifier returns valid=False with details about why - that's the audit trail your customers (or regulators) can rely on.
Receipt fields
| Field | What it is |
|---|---|
receipt_id | Unique receipt identifier. |
job_id | Which job this receipt attests. |
spec_hash | Opaque hash of the execution specification used. |
inputs_root | Merkle root over the inputs you submitted. |
outputs_root | Merkle root over the outputs that were produced. |
verification_attestation | Merkle root committing to the independent verification work. |
executor_attestation_ids | Opaque IDs identifying the compute providers involved. |
issued_at | ISO-8601 timestamp of issuance. |
receipt_hash | Self-referential SHA-256 - what verify_receipt recomputes. |
Verification as a Service
If you run inference on your own infrastructure and just need an independent attestation, use the VaaS endpoint. You submit your inputs and the SHA-256 of the outputs you produced; the platform independently verifies and issues a receipt.
import hashlib
# Compute SHA-256 of your output bytes (numpy array → bytes → sha256)
my_outputs_hash = hashlib.sha256(my_embeddings.tobytes()).hexdigest()
receipt = client.verify_outputs(
model="bge-large-v1.5",
inputs=texts,
claimed_output_commitment=my_outputs_hash,
)
# Raises VerificationFailedError if our independent verification disagrees.
Account
client.get_account() returns a single dict describing your account, current usage, and the keys on file. The shape:
| Field | Meaning |
|---|---|
account_id | UUID of your account. |
name | Display name. |
email | Account email. |
username | Account username. |
email_verified_at | ISO-8601 timestamp, or null if not yet verified. |
tier | Assigned tier (e.g. "free"). |
effective_tier | Tier currently being charged against (changes when a trial expires). |
subscription_status | "trial" | "active" | "none". |
trial_started_at | ISO-8601 timestamp. |
trial_ends_at | ISO-8601 timestamp. |
chunks_consumed_period | Chunks consumed in the current period. |
chunks_period_started_at | ISO-8601 timestamp of the current period's start. |
limits | Object with the caps for the current effective tier - see below. |
keys | Array of API-key descriptors - see below. |
limits
| Field | Meaning |
|---|---|
chunks_per_period | Cap on chunks per period. |
period_length_days | Period length, in days. |
chunks_per_day | Daily chunk cap. |
chunks_per_minute | Sustained per-minute cap. |
max_chunks_per_request | Per-request chunk cap. |
max_inputs | Maximum number of inputs allowed in one request. |
keys[i]
| Field | Meaning |
|---|---|
key_id | 12-char prefix - pass this to revoke_key. |
label | Label you gave the key at creation. |
created_at | ISO-8601 timestamp. |
last_used_at | ISO-8601 timestamp, or null if never used. |
revoked_at | ISO-8601 timestamp, or null if active. |
All timestamps are UTC ISO-8601 strings.
Errors and retries
The client raises typed exceptions you can catch granularly:
| Exception | Cause | Recommended action |
|---|---|---|
AuthError | Invalid or revoked API key | Re-issue key on the account page |
TrialExpiredError | Trial window passed | Email us to upgrade or extend |
QuotaError | Period or daily chunk quota hit | Wait until reset, or request more |
RateLimitError | Per-minute throughput exceeded | Back off; the SDK exposes retry_after |
ServiceBusyError | Queue depth or capacity hit | Retry with backoff |
JobFailedError | One or more chunks failed verification | Inspect err.failed_chunks |
VerificationFailedError | Receipt did not verify offline | Treat output as untrusted |
NetworkError | Transport-level error (no HTTP response) | wait_for_completion retries automatically; elsewhere, retry with backoff |
ApiError | Other 4xx/5xx | Inspect err.status and err.body |
from cyberian import Client, RateLimitError, QuotaError
client = Client()
try:
result = client.submit_and_wait(model="bge-large-v1.5", inputs=texts)
except RateLimitError as e:
time.sleep(e.retry_after)
except QuotaError:
print("Trial chunks exhausted - email us to extend.")
Trial limits
- 14 days from email verification (not signup — the clock starts when you click the confirmation link)
- 400 chunks per trial period
- 100 chunks per day
- 60 chunks per minute sustained
- 100 chunks max per request, up to 1,000 input texts per request
Limits beyond the trial are quoted on request - email support@cyberiansystems.ai.
Client reference
Constructor
Client(api_key: str | None = None)
If api_key is omitted, the client reads CYBERIAN_API_KEY from the environment.
Methods
| Method | Returns | Description |
|---|---|---|
submit_job(model, inputs) | dict | Submit a job. Returns {"id", "status", "chunk_count", ...}. |
get_job(job_id) | dict | Fetch current job state. |
wait_for_completion(job_id, timeout_sec=600) | dict | Wait until the job reaches SETTLED, FAILED, or timeout. |
get_job_output(job_id) | np.ndarray | Output tensor, dtype float32, shape (N, D). |
get_receipt(receipt_id) | dict | The slim public receipt - see "Receipt fields" above. |
verify_receipt(receipt_id) | dict | Validate the receipt - {"valid", "checks", "errors"}. |
submit_and_wait(model, inputs) | JobResult | One-shot convenience. |
verify_outputs(model, inputs, claimed_output_commitment) | dict | VaaS path - receipt for outputs you produced. |
list_models() | dict | Models enabled for your account. |
get_account() | dict | Profile, usage counters, key list. |
JobResult
@dataclass
class JobResult:
job: dict # final job dict from /jobs/:id
receipt: dict # the slim public receipt
embeddings: np.ndarray # float32 tensor, shape (N, D)
verification: dict | None # {"valid", "checks", "errors"} or None
Support
Questions, bug reports, upgrade requests: support@cyberiansystems.ai.
Direct HTTP for anything not Python. Same endpoints the Python SDK calls under the hood, same API key, same trial quotas. Every customer-facing route lives at https://api.cyberiansystems.ai.
Overview
Authentication is Bearer token. Wire format is JSON for everything except GET /jobs/:id/output, which streams binary float32 (the embeddings themselves). All timestamps are ISO-8601 UTC.
cyb_… key you got at signup (or rotated since) works for both the Python SDK and direct REST. No separate REST credential.The Python SDK is a thin wrapper around these endpoints — if you ever want to see what's on the wire, set logging.getLogger("httpx").setLevel("DEBUG") in a Python script and submit a job. Everything below is what you'll see.
Authentication
Every endpoint marked BEARER requires:
Authorization: Bearer cyb_…your-key…
The only unauthenticated endpoint is GET /health (connectivity probe, not documented further). Everything else — including GET /models — requires a Bearer token.
Keys never expire on their own. Rotate them periodically via POST /auth/keys/rotate; revoke a compromised one via POST /auth/keys/revoke.
Typical flow
Five sequential calls cover the submit → wait → fetch → verify → download chain. Copy-paste; the shell variables thread through. Requires $CYBERIAN_API_KEY in the environment and jq on the path.
# 1. submit a job → capture the job id
export JOB_ID=$(curl -sS -X POST https://api.cyberiansystems.ai/jobs \
-H "Authorization: Bearer $CYBERIAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"bge-large-v1.5","inputs":["hello","world"]}' \
| jq -r .id)
echo "job=$JOB_ID"
# 2. long-poll until the job hits a terminal state (SETTLED / FAILED / REFUNDED)
# each wait caps at 30s server-side; loop on your side for longer jobs.
export RECEIPT_ID=$(curl -sS "https://api.cyberiansystems.ai/jobs/$JOB_ID/wait?max_wait=30" \
-H "Authorization: Bearer $CYBERIAN_API_KEY" \
| jq -r .receipt_id)
echo "receipt=$RECEIPT_ID"
# 3. fetch the slim Merkle receipt (your audit artifact)
curl -sS "https://api.cyberiansystems.ai/receipts/$RECEIPT_ID" \
-H "Authorization: Bearer $CYBERIAN_API_KEY" | jq
# 4. server-side verification pass — recomputes every hash on the receipt
curl -sS "https://api.cyberiansystems.ai/receipts/$RECEIPT_ID/verify" \
-H "Authorization: Bearer $CYBERIAN_API_KEY" | jq
# 5. pull the raw float32 embeddings + shape header for reshape
curl -sS "https://api.cyberiansystems.ai/jobs/$JOB_ID/output" \
-H "Authorization: Bearer $CYBERIAN_API_KEY" \
--output embeddings.bin -D headers.txt
grep X-Cyberian headers.txt # X-Cyberian-Shape + X-Cyberian-Dtype
If $RECEIPT_ID is null after step 2, the job didn't reach SETTLED within the wait window. Re-issue step 2 with the same $JOB_ID; it returns immediately once the job terminates. For step-5 binary handling on the Python side, the SDK's get_job_output() reshapes for you using the headers — see the Python tab.
GET/modelsBEARER
List the models enabled for your account. The response is the full catalog for every authenticated caller today; per-account row filtering ships in Phase 3.
curl https://api.cyberiansystems.ai/models \
-H "Authorization: Bearer $CYBERIAN_API_KEY"
Response (200):
{
"models": [
{
"id": "bge-large-v1.5",
"name": "BGE Large (English) v1.5",
"description": "1024-dim sentence embeddings. CPU-deterministic.",
"output_dimensions": 1024,
"max_input_length_tokens": 512
}
]
}
POST/jobsBEARER
Submit a batch of inputs for verified inference. Synchronous response returns the job in EXECUTING; the work happens on the platform and you poll (or long-poll) for the receipt.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
model | string | yes | One of the model IDs from GET /models. |
inputs | array<string> | yes | Texts to embed. 1 to 1000 per request; each up to 64 KB. |
curl -X POST https://api.cyberiansystems.ai/jobs \
-H "Authorization: Bearer $CYBERIAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"bge-large-v1.5","inputs":["hello","world"]}'
Response (201):
{
"id": "7c4f…uuid",
"status": "EXECUTING",
"chunk_count": 1,
"receipt_id": null,
"created_at": "2026-05-14T20:15:00.000Z"
}
Errors: 400 (invalid request / unknown model), 401 (missing or bad Bearer), 402 (trial expired / quota exhausted), 413 (request too large), 429 (rate limited), 503 (platform saturated).
GET/jobs/:idBEARER
Current job status. Returns 404 (not 403) for jobs that exist but belong to a different account — we don't disclose UUID existence across accounts.
curl https://api.cyberiansystems.ai/jobs/$JOB_ID \
-H "Authorization: Bearer $CYBERIAN_API_KEY"
Response (200):
{
"id": "7c4f…uuid",
"status": "SETTLED", // or EXECUTING / PROVING / FAILED / REFUNDED
"chunk_count": 1,
"receipt_id": "r-9f…uuid",
"created_at": "2026-05-14T20:15:00.000Z"
}
GET/jobs/:id/wait?max_wait=NBEARER
Long-poll: the request blocks server-side until the job reaches a terminal state (SETTLED, FAILED, REFUNDED) or max_wait seconds elapse, whichever comes first. Capped at 30 seconds per request; loop on your side for longer waits.
curl "https://api.cyberiansystems.ai/jobs/$JOB_ID/wait?max_wait=30" \
-H "Authorization: Bearer $CYBERIAN_API_KEY"
Response body shape is identical to GET /jobs/:id. Cheaper on the platform than busy-polling.
GET/jobs/:id/resultsBEARER
Per-chunk progress. Useful when a job's still EXECUTING and you want partial visibility, or after settlement for an audit log.
{
"job_id": "7c4f…uuid",
"status": "SETTLED",
"receipt_id": "r-9f…uuid",
"chunks": [
{
"chunk_index": 0,
"row_start": 0,
"row_end": 2,
"status": "VERIFIED",
"output_commitment": "sha256-hex…",
"selected_for_verification": true
}
]
}
GET/jobs/:id/outputBEARER
Concatenated output embeddings as raw float32 bytes, little-endian, in input order. Only available once the job reaches SETTLED.
Reshape using the headers we send back:
| Header | Meaning |
|---|---|
Content-Type: application/octet-stream | Binary body. |
X-Cyberian-Shape: rows,cols | e.g. 2,1024 for 2 inputs through bge-large. |
X-Cyberian-Dtype: float32 | Pinned for the embedding workload. |
curl https://api.cyberiansystems.ai/jobs/$JOB_ID/output \
-H "Authorization: Bearer $CYBERIAN_API_KEY" \
--output embeddings.bin -D headers.txt
grep X-Cyberian headers.txt
POST/verifyBEARER
Verification-as-a-Service. You ran inference yourself; we re-execute on first-party infrastructure and either return a receipt confirming your output (200) or reject the claim (422). Synchronous — blocks until verification completes or times out.
/verify currently accepts a single claim form — a SHA-256 commitment of your concatenated outputs (the claimed_output_commitment below). Additional claim types are on the way, each with its own parameter shape; each will be documented when it ships.Request body:
| Field | Type | Required | Description |
|---|---|---|---|
model | string | yes | One of the catalog model IDs. |
inputs | array<string> | yes | The inputs you fed your model. Same caps as POST /jobs. |
claimed_output_commitment | string | yes | SHA-256 hex of the concatenated output bytes your model produced. We compare against ours. |
Response (200, verified): slim Receipt (same shape as GET /receipts/:id).
Response (422, mismatch):
{
"error": "verification_failed",
"reason": "prover replay did not match claimed output_commitment",
"job_id": "7c4f…uuid"
}
GET/receipts/:idBEARER
The slim, customer-visible receipt — the cryptographic artifact you keep for audit. Verifies offline via the Python SDK's verify_receipt_offline(), or online via the next endpoint.
Response (200):
{
"receipt_id": "r-9f…uuid",
"job_id": "7c4f…uuid",
"spec_hash": "sha256-hex…",
"inputs_root": "sha256-hex…",
"outputs_root": "sha256-hex…",
"verification_attestation": "sha256-hex…",
"verification_level": "standard",
"executor_attestation_ids": ["cyb-exec-…"],
"issued_at": "2026-05-14T20:15:42.000Z",
"receipt_hash": "sha256-hex…"
}
GET/receipts/:id/verifyBEARER
Server-side recomputation of every hash on the receipt. Use this when you want the platform to attest its own receipt without trusting your local code.
{
"valid": true,
"checks": {
"receipt_hash": true,
"inputs_root": true,
"outputs_root": true,
"verification_attestation": true
},
"errors": []
}
GET/accountBEARER
Your account: tier, trial status, period usage, key list (metadata only — never the plaintext keys).
{
"account_id": "acct-uuid…",
"email": "you@company.com",
"username": "you",
"email_verified_at": "2026-05-01T12:00:00.000Z",
"name": "Company",
"tier": "free",
"effective_tier": "trial",
"subscription_status": "trial",
"trial_started_at": "2026-05-01T12:00:00.000Z",
"trial_ends_at": "2026-05-15T12:00:00.000Z",
"chunks_consumed_period": 37,
"chunks_period_started_at": "2026-05-01T12:00:00.000Z",
"limits": {
"chunks_per_period": 400,
"period_length_days": 14,
"chunks_per_day": 100,
"chunks_per_minute": 60,
"max_chunks_per_request": 100,
"max_input_texts": 1000
},
"keys": [
{
"key_id": "cyb_…12char",
"label": "trial",
"created_at": "2026-05-01T12:00:00.000Z",
"last_used_at": "2026-05-14T20:14:00.000Z",
"revoked_at": null
}
]
}
POST/auth/keys/rotateBEARER
Mint a fresh key. The old key remains valid until you explicitly revoke it, so you have a window to update your deployments. The new plaintext key is returned once; we never display it again.
curl -X POST https://api.cyberiansystems.ai/auth/keys/rotate \
-H "Authorization: Bearer $CYBERIAN_API_KEY"
Response (201):
{
"api_key": "cyb_label_32hexchars…",
"key_id": "cyb_…12char"
}
POST/auth/keys/revokeBEARER
Invalidate a key by its key_id (the 12-char prefix, not the full secret). Idempotent — revoking an already-revoked key returns 200.
curl -X POST https://api.cyberiansystems.ai/auth/keys/revoke \
-H "Authorization: Bearer $CYBERIAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"key_id":"cyb_…12char"}'
key_id.Errors
JSON body on every non-2xx, shape { "error": "<machine-readable code>", "message"?: "…" }. The status code carries the most information; the error field is for switch statements.
| Status | error | What it means |
|---|---|---|
| 400 | invalid_request | Malformed body or unknown field. |
| 400 | unknown_model | Model ID not in your catalog. Email models@cyberiansystems.ai to request additions. |
| 401 | unauthorized | Missing, malformed, or revoked Bearer. |
| 402 | trial_expired / quota_exceeded | Trial clock ran out, or period quota exhausted. |
| 404 | not_found | Resource doesn't exist or belongs to another account. |
| 413 | request_too_large | Exceeds max_chunks_per_request or max_input_texts. |
| 422 | verification_failed | VaaS replay disagreed with your claimed commitment. |
| 429 | rate_limited / too_many_inflight | Per-minute bucket or in-flight cap. Respect the Retry-After header. |
| 503 | service_busy | Platform queue saturated. Respect Retry-After. |
| 504 | verification_timeout | VaaS replay didn't finish within the request window. |
Rate limits
Same trial caps as the Python SDK — see the Trial limits section on the Python tab. The platform enforces them server-side; you don't need to add client-side throttling for normal usage.
On 429 / 503, we send Retry-After in seconds. Honor it. Back-off-and-retry is the right loop; the SDK does this automatically.
On-chain settlement and on-chain receipt anchoring are on the way. The interface isn't published yet.
Coming soon
Customers who want their verified-inference receipts anchored on a public blockchain (Base, via USDC escrow + Noir / EZKL proofs of receipt validity) will pick that settlement option at job submit time. Until then:
- Off-chain settlement (Stripe) is the only billing path. Trial users don't pay anything; the paid tier handles invoicing.
- Receipts are cryptographic today, just not anchored. You can already publish a receipt's
receipt_hashon any timestamping service of your choice (e.g. OpenTimestamps) for third-party-witnessable proof of when it was issued.
If on-chain settlement is a hard requirement for you, email support@cyberiansystems.ai with the use case — we'll prioritize the feature based on customer pull.