---
title: Wire protocol
description: "The canonical contract for sending events to a Cuitty portal — request, signature, and response."
section: Reference
order: 1
updatedAt: 2026-04-27
slug: reference/wire-protocol
---
# 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

```json
{
  "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 of `audit`, `config`, `cost`, `deploy`, `error`, `feature_flag`, `log`, `performance`, `repository`, `tenant`, `trace`, `video`, or `webhook`. Determines which module receives the event.
- `ts` — ISO 8601 timestamp in UTC.
- `data` — module-specific payload. See the [event reference](#event-types) below.

## Response

```json
{ "accepted": 1, "rejected": [] }
```

If any event in the batch is malformed, the portal accepts the valid ones and reports the rest:

```json
{
  "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

```json
{
  "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

```json
{
  "level": "info",
  "message": "request processed",
  "module": "api",
  "fields": { "requestId": "abc" }
}
```

### deploy

```json
{
  "service": "api",
  "version": "v1.4.2",
  "environment": "production",
  "status": "succeeded",
  "commit": "ab12cd34"
}
```

### repository

```json
{
  "provider": "github",
  "repo": "acme/api",
  "branch": "main",
  "commit": "ab12cd34",
  "author": "alice"
}
```

### config

```json
{
  "path": "config/production.toml",
  "diff": "--- old\n+++ new\n@@ -1 +1 @@\n-foo=1\n+foo=2",
  "actor": "alice"
}
```

### cost

```json
{
  "provider": "gcp",
  "service": "compute",
  "region": "us-central1",
  "amount": 1234.56,
  "currency": "USD",
  "period": "2026-04"
}
```

### performance

```json
{
  "service": "api",
  "metric": "p99_latency",
  "value": 142.3,
  "unit": "ms"
}
```

### trace

```json
{
  "traceId": "trace_01",
  "spanId": "span_01",
  "serviceName": "portal",
  "name": "GET /api/projects",
  "durationMs": 42
}
```

### error

```json
{
  "name": "TypeError",
  "message": "Cannot read properties of undefined",
  "release": "portal@0.3.0",
  "environment": "production"
}
```

### feature_flag

```json
{
  "key": "checkout_redesign",
  "enabled": true,
  "rollout": 25,
  "actor": "alice@example.com"
}
```

### webhook

```json
{
  "destination": "deploy-alerts",
  "url": "https://example.com/hooks/cuitty",
  "statusCode": 200,
  "attempt": 1
}
```

### tenant

```json
{
  "projectId": "proj_01",
  "plan": "team",
  "ingestLimitPerSecond": 1000
}
```

### video

```json
{
  "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:

```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.