Reference

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

HeaderRequiredNotes
AuthorizationyesBearer <api_key>. The key identifies the project.
Content-Typeyesapplication/json.
X-Cuitty-Signatureyeshex(hmac_sha256(webhook_secret, body)) — verifies authenticity.
X-Cuitty-Idempotency-KeynoUUID. 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:

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

CodeMeaning
200Batch processed — see body for per-event accept/reject breakdown.
400Body is not valid JSON, or events is missing.
401Missing or invalid Authorization bearer.
403Signature does not match.
413Batch exceeds the 5 MB size limit. Split into smaller batches.
429Rate limit hit. Retry with exponential backoff.
503The 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.