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