Wire protocol
The canonical contract for sending events to a Cuitty portal — request, signature, and response.
Wire protocol
The wire protocol is the single source of truth for how data enters Cuitty. Every SDK, plugin, and integration is just a typed wrapper over this one endpoint. If you can sign a JSON blob and POST it over HTTPS, you can integrate Cuitty.
Endpoint
POST {portalUrl}/api/ingest
portalUrl is the base URL of your portal — http://localhost:7700 for a local install, https://app.cuitty.com for Cuitty Cloud, or whatever you configured for self-hosted production.
Headers
| Header | Required | Notes |
|---|---|---|
Authorization | yes | Bearer <api_key>. The key identifies the project. |
Content-Type | yes | application/json. |
X-Cuitty-Signature | yes | hex(hmac_sha256(webhook_secret, body)) — verifies authenticity. |
X-Cuitty-Idempotency-Key | no | UUID. Repeated POSTs with the same key are deduplicated for 24 h. |
Body
{
"events": [
{
"type": "audit",
"ts": "2026-04-27T12:34:56Z",
"data": {
"actor": "alice@example.com",
"action": "secret.rotate",
"resource": "stripe.live_key"
}
}
]
}
events is a JSON array. Each entry has:
type— one ofaudit,config,cost,deploy,error,feature_flag,log,performance,repository,tenant,trace,video, orwebhook. Determines which module receives the event.ts— ISO 8601 timestamp in UTC.data— module-specific payload. See the event reference below.
Response
{ "accepted": 1, "rejected": [] }
If any event in the batch is malformed, the portal accepts the valid ones and reports the rest:
{
"accepted": 2,
"rejected": [
{ "index": 1, "reason": "data.actor: required" }
]
}
Status codes
| Code | Meaning |
|---|---|
| 200 | Batch processed — see body for per-event accept/reject breakdown. |
| 400 | Body is not valid JSON, or events is missing. |
| 401 | Missing or invalid Authorization bearer. |
| 403 | Signature does not match. |
| 413 | Batch exceeds the 5 MB size limit. Split into smaller batches. |
| 429 | Rate limit hit. Retry with exponential backoff. |
| 503 | The portal is shedding load. The SDK retries; raw clients should too. |
Event types
audit
{
"actor": "alice@example.com",
"action": "secret.rotate",
"resource": "stripe.live_key",
"method": "POST",
"path": "/api/secrets/rotate",
"statusCode": 200,
"duration": 32,
"ip": "203.0.113.5",
"userAgent": "Mozilla/5.0 ...",
"metadata": {}
}
log
{
"level": "info",
"message": "request processed",
"module": "api",
"fields": { "requestId": "abc" }
}
deploy
{
"service": "api",
"version": "v1.4.2",
"environment": "production",
"status": "succeeded",
"commit": "ab12cd34"
}
repository
{
"provider": "github",
"repo": "acme/api",
"branch": "main",
"commit": "ab12cd34",
"author": "alice"
}
config
{
"path": "config/production.toml",
"diff": "--- old\n+++ new\n@@ -1 +1 @@\n-foo=1\n+foo=2",
"actor": "alice"
}
cost
{
"provider": "gcp",
"service": "compute",
"region": "us-central1",
"amount": 1234.56,
"currency": "USD",
"period": "2026-04"
}
performance
{
"service": "api",
"metric": "p99_latency",
"value": 142.3,
"unit": "ms"
}
trace
{
"traceId": "trace_01",
"spanId": "span_01",
"serviceName": "portal",
"name": "GET /api/projects",
"durationMs": 42
}
error
{
"name": "TypeError",
"message": "Cannot read properties of undefined",
"release": "portal@0.3.0",
"environment": "production"
}
feature_flag
{
"key": "checkout_redesign",
"enabled": true,
"rollout": 25,
"actor": "alice@example.com"
}
webhook
{
"destination": "deploy-alerts",
"url": "https://example.com/hooks/cuitty",
"statusCode": 200,
"attempt": 1
}
tenant
{
"projectId": "proj_01",
"plan": "team",
"ingestLimitPerSecond": 1000
}
video
{
"jobId": "video_01",
"targetUrl": "https://app.cuitty.com",
"status": "completed",
"artifactUrl": "https://cdn.cuitty.com/videos/video_01.mp4"
}
Signing
signature = hex(hmac_sha256(webhook_secret, body))
The webhook_secret is generated alongside the API key in the portal. Send the hex digest with no sha256= prefix — the portal expects the bare hex string.
A reference implementation in TypeScript:
import { createHmac } from "node:crypto";
function sign(body: string, secret: string): string {
return createHmac("sha256", secret).update(body).digest("hex");
}
Idempotency
Pass a UUID in X-Cuitty-Idempotency-Key to make retries safe. The portal stores the key for 24 hours and short-circuits duplicate POSTs to a 200 with the original response body.
Rate limits
The default per-project rate limit is 1,000 events per second, 100 batches per second, 5 MB per batch. All three are configurable in self-hosted installs via INGEST_RATE_LIMIT_* environment variables.