{
  "slug": "reference/wire-protocol",
  "title": "Wire protocol",
  "description": "The canonical contract for sending events to a Cuitty portal — request, signature, and response.",
  "url": "https://cuitty.com/docs/reference/wire-protocol",
  "markdown_url": "https://cuitty.com/docs/reference/wire-protocol.md",
  "json_url": "https://cuitty.com/docs/reference/wire-protocol.json",
  "frontmatter": {
    "title": "Wire protocol",
    "description": "The canonical contract for sending events to a Cuitty portal — request, signature, and response.",
    "order": 1,
    "section": "Reference",
    "updatedAt": "2026-04-27"
  },
  "headings": [
    {
      "depth": 1,
      "slug": "wire-protocol",
      "text": "Wire protocol"
    },
    {
      "depth": 2,
      "slug": "endpoint",
      "text": "Endpoint"
    },
    {
      "depth": 2,
      "slug": "headers",
      "text": "Headers"
    },
    {
      "depth": 2,
      "slug": "body",
      "text": "Body"
    },
    {
      "depth": 2,
      "slug": "response",
      "text": "Response"
    },
    {
      "depth": 2,
      "slug": "status-codes",
      "text": "Status codes"
    },
    {
      "depth": 2,
      "slug": "event-types",
      "text": "Event types"
    },
    {
      "depth": 3,
      "slug": "audit",
      "text": "audit"
    },
    {
      "depth": 3,
      "slug": "log",
      "text": "log"
    },
    {
      "depth": 3,
      "slug": "deploy",
      "text": "deploy"
    },
    {
      "depth": 3,
      "slug": "repository",
      "text": "repository"
    },
    {
      "depth": 3,
      "slug": "config",
      "text": "config"
    },
    {
      "depth": 3,
      "slug": "cost",
      "text": "cost"
    },
    {
      "depth": 3,
      "slug": "performance",
      "text": "performance"
    },
    {
      "depth": 3,
      "slug": "trace",
      "text": "trace"
    },
    {
      "depth": 3,
      "slug": "error",
      "text": "error"
    },
    {
      "depth": 3,
      "slug": "feature_flag",
      "text": "feature_flag"
    },
    {
      "depth": 3,
      "slug": "webhook",
      "text": "webhook"
    },
    {
      "depth": 3,
      "slug": "tenant",
      "text": "tenant"
    },
    {
      "depth": 3,
      "slug": "video",
      "text": "video"
    },
    {
      "depth": 2,
      "slug": "signing",
      "text": "Signing"
    },
    {
      "depth": 2,
      "slug": "idempotency",
      "text": "Idempotency"
    },
    {
      "depth": 2,
      "slug": "rate-limits",
      "text": "Rate limits"
    }
  ],
  "body_markdown": "# Wire protocol\n\nThe 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.\n\n## Endpoint\n\n```\nPOST {portalUrl}/api/ingest\n```\n\n`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.\n\n## Headers\n\n| Header                       | Required | Notes                                                              |\n| ---------------------------- | -------- | ------------------------------------------------------------------ |\n| `Authorization`              | yes      | `Bearer <api_key>`. The key identifies the project.                |\n| `Content-Type`               | yes      | `application/json`.                                                |\n| `X-Cuitty-Signature`         | yes      | `hex(hmac_sha256(webhook_secret, body))` — verifies authenticity.  |\n| `X-Cuitty-Idempotency-Key`   | no       | UUID. Repeated POSTs with the same key are deduplicated for 24 h.  |\n\n## Body\n\n```json\n{\n  \"events\": [\n    {\n      \"type\": \"audit\",\n      \"ts\": \"2026-04-27T12:34:56Z\",\n      \"data\": {\n        \"actor\": \"alice@example.com\",\n        \"action\": \"secret.rotate\",\n        \"resource\": \"stripe.live_key\"\n      }\n    }\n  ]\n}\n```\n\n`events` is a JSON array. Each entry has:\n\n- `type` — one of `audit`, `config`, `cost`, `deploy`, `error`, `feature_flag`, `log`, `performance`, `repository`, `tenant`, `trace`, `video`, or `webhook`. Determines which module receives the event.\n- `ts` — ISO 8601 timestamp in UTC.\n- `data` — module-specific payload. See the [event reference](#event-types) below.\n\n## Response\n\n```json\n{ \"accepted\": 1, \"rejected\": [] }\n```\n\nIf any event in the batch is malformed, the portal accepts the valid ones and reports the rest:\n\n```json\n{\n  \"accepted\": 2,\n  \"rejected\": [\n    { \"index\": 1, \"reason\": \"data.actor: required\" }\n  ]\n}\n```\n\n## Status codes\n\n| Code | Meaning                                                               |\n| ---- | --------------------------------------------------------------------- |\n| 200  | Batch processed — see body for per-event accept/reject breakdown.     |\n| 400  | Body is not valid JSON, or `events` is missing.                       |\n| 401  | Missing or invalid `Authorization` bearer.                            |\n| 403  | Signature does not match.                                             |\n| 413  | Batch exceeds the 5 MB size limit. Split into smaller batches.        |\n| 429  | Rate limit hit. Retry with exponential backoff.                       |\n| 503  | The portal is shedding load. The SDK retries; raw clients should too. |\n\n## Event types\n\n### audit\n\n```json\n{\n  \"actor\": \"alice@example.com\",\n  \"action\": \"secret.rotate\",\n  \"resource\": \"stripe.live_key\",\n  \"method\": \"POST\",\n  \"path\": \"/api/secrets/rotate\",\n  \"statusCode\": 200,\n  \"duration\": 32,\n  \"ip\": \"203.0.113.5\",\n  \"userAgent\": \"Mozilla/5.0 ...\",\n  \"metadata\": {}\n}\n```\n\n### log\n\n```json\n{\n  \"level\": \"info\",\n  \"message\": \"request processed\",\n  \"module\": \"api\",\n  \"fields\": { \"requestId\": \"abc\" }\n}\n```\n\n### deploy\n\n```json\n{\n  \"service\": \"api\",\n  \"version\": \"v1.4.2\",\n  \"environment\": \"production\",\n  \"status\": \"succeeded\",\n  \"commit\": \"ab12cd34\"\n}\n```\n\n### repository\n\n```json\n{\n  \"provider\": \"github\",\n  \"repo\": \"acme/api\",\n  \"branch\": \"main\",\n  \"commit\": \"ab12cd34\",\n  \"author\": \"alice\"\n}\n```\n\n### config\n\n```json\n{\n  \"path\": \"config/production.toml\",\n  \"diff\": \"--- old\\n+++ new\\n@@ -1 +1 @@\\n-foo=1\\n+foo=2\",\n  \"actor\": \"alice\"\n}\n```\n\n### cost\n\n```json\n{\n  \"provider\": \"gcp\",\n  \"service\": \"compute\",\n  \"region\": \"us-central1\",\n  \"amount\": 1234.56,\n  \"currency\": \"USD\",\n  \"period\": \"2026-04\"\n}\n```\n\n### performance\n\n```json\n{\n  \"service\": \"api\",\n  \"metric\": \"p99_latency\",\n  \"value\": 142.3,\n  \"unit\": \"ms\"\n}\n```\n\n### trace\n\n```json\n{\n  \"traceId\": \"trace_01\",\n  \"spanId\": \"span_01\",\n  \"serviceName\": \"portal\",\n  \"name\": \"GET /api/projects\",\n  \"durationMs\": 42\n}\n```\n\n### error\n\n```json\n{\n  \"name\": \"TypeError\",\n  \"message\": \"Cannot read properties of undefined\",\n  \"release\": \"portal@0.3.0\",\n  \"environment\": \"production\"\n}\n```\n\n### feature_flag\n\n```json\n{\n  \"key\": \"checkout_redesign\",\n  \"enabled\": true,\n  \"rollout\": 25,\n  \"actor\": \"alice@example.com\"\n}\n```\n\n### webhook\n\n```json\n{\n  \"destination\": \"deploy-alerts\",\n  \"url\": \"https://example.com/hooks/cuitty\",\n  \"statusCode\": 200,\n  \"attempt\": 1\n}\n```\n\n### tenant\n\n```json\n{\n  \"projectId\": \"proj_01\",\n  \"plan\": \"team\",\n  \"ingestLimitPerSecond\": 1000\n}\n```\n\n### video\n\n```json\n{\n  \"jobId\": \"video_01\",\n  \"targetUrl\": \"https://app.cuitty.com\",\n  \"status\": \"completed\",\n  \"artifactUrl\": \"https://cdn.cuitty.com/videos/video_01.mp4\"\n}\n```\n\n## Signing\n\n```\nsignature = hex(hmac_sha256(webhook_secret, body))\n```\n\nThe `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.\n\nA reference implementation in TypeScript:\n\n```typescript\nimport { createHmac } from \"node:crypto\";\n\nfunction sign(body: string, secret: string): string {\n  return createHmac(\"sha256\", secret).update(body).digest(\"hex\");\n}\n```\n\n## Idempotency\n\nPass 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.\n\n## Rate limits\n\nThe 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.",
  "links_out": [
    "#event-types"
  ]
}