### Snippet Custom Integrations - Cursor Rules

These rules guide developers to implement robust, consistent custom integrations for Snippet widgets.

#### 1) Core conventions

- **One protocol = one widget**: Each widget maps to a unique `protocolId` (slug).
- **Unique IDs**: Choose a lowercase, hyphen/underscore-only slug for `protocolId` (e.g., `awesome-dex`). The server sanitizes invalid characters to `-`.
- **Metric ID format**: `{protocolId}___{Label}` (triple underscore). Examples: `awesome-dex___TVL`, `awesome-dex___USERS_MOM`.
- **Label length**: Keep `{Label}` ≤ 16 chars for best widget legibility.
- **Units**: Free-form (e.g., `USD`, `%`, `BTC`, `TPS`, `football_fields`). Keep units consistent per metric `id`.

#### 2) Authentication and environments

- **Auth header** (required for write, optional for reads of private data):
  - `Authorization: Bearer <SNIPPET_API_KEY>`
- **Environment variables** (recommended):
  - `SNIPPET_API_BASE=https://<your-api-host>`
  - `SNIPPET_API_KEY=sk_snippet.gg...` (from the Snippet mobile app)

#### 3) Protocol registration rules

- Register once per `protocolId`:
  - `POST /api/register-protocol` with `{ id, name, logo?, chains? }`
- On `400` id conflict, pick a new slug or query availability via `GET /api/validate-protocol?id=...`.
- Treat `id` as immutable after creation to avoid breaking existing widgets.

#### 4) Metric submission rules

- Endpoint: `POST /api/metrics`
- Body shape: `{ metrics: Array<{ id, value, protocolId, unit?, module?, timestamp? }> }`
- Required fields per metric: `id`, `value`, `protocolId`
- Optional fields:
  - `unit` (free-form)
  - `module` (defaults to `"protocol"`)
  - `timestamp` (ISO 8601). If provided, it is used as-is; if omitted, the server snaps to the top of the hour. Must not be > 5 minutes in the future.
- Batching: Max 100 metrics per request.
- Ownership: You can only write to protocols owned by your API key (or public protocols).
- Posting cadence (recommendation): At most once per hour per metric `id` to align with widget display.

#### 5) Reading metrics (widget consumption)

- Public data: `GET /metrics/byProtocol/:protocolId` (no auth needed).
- Private data (owned by your key): include the same `Authorization` header.
- Response includes current value, change fields (`change1h`, `change24h`), `unit`, and `timestamp` per metric.

#### 6) Error handling and retries

- Common statuses:
  - `400` invalid request (do not retry; fix payload)
  - `401` invalid key (do not retry; fix auth)
  - `404` protocol not found (ensure registration)
  - `5xx` server errors (retry with exponential backoff)
- Retry policy (recommended): exponential backoff with jitter, max 5 attempts; stop on 4xx except `429`.
- Capture and surface response `warnings` from metric submission (e.g., long labels, updates to existing metrics).

#### 7) Validation checklist (pre-deploy)

- Validate API key: `GET /api/validate-key`
- Validate protocol availability: `GET /api/validate-protocol?id=<protocolId>`
- Ensure all `metrics[].id` follow `{protocolId}___{Label}`
- Ensure timestamps are ISO 8601 and ≤ 5 minutes in the future (if provided)
- Confirm batch sizes ≤ 100 and stable `unit` usage per metric `id`
- Manually test reads: `GET /metrics/byProtocol/<protocolId>` (with/without auth depending on visibility)

#### 8) Security rules

- Store secrets only in environment variables or a secure vault.
- Never log full API keys or sensitive payloads.
- Use HTTPS for all endpoints.
- Rotate keys on compromise; validate with `GET /api/validate-key` after rotation.

#### 9) Observability and quality

- Log request IDs and error messages from API failures where available.
- Include metric `id` and `protocolId` in logs for traceability (avoid sensitive values).
- Add unit tests for ID formatting and timestamp logic.
- Alert on repeated `4xx` (payload issues) and `5xx` (service/infra).

#### 10) Sample submission (cURL)

```bash
curl -X POST "$SNIPPET_API_BASE/api/metrics" \
  -H "Authorization: Bearer $SNIPPET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "metrics": [
          {
            "id": "awesome-dex___TVL",
            "value": 12345678.9,
            "protocolId": "awesome-dex",
            "unit": "USD",
            "timestamp": "2025-08-05T17:00:00Z"
          },
          {
            "id": "awesome-dex___USERS_MOM",
            "value": 5.2,
            "protocolId": "awesome-dex",
            "unit": "%"
          }
        ]
      }'
```

#### 11) Minimal TypeScript helper (Node/Bun)

```ts
const base = process.env.SNIPPET_API_BASE!;
const key = process.env.SNIPPET_API_KEY!;

export async function pushMetrics(
  metrics: Array<{
    id: string;
    value: number;
    protocolId: string;
    unit?: string;
    module?: string;
    timestamp?: string; // ISO 8601
  }>
) {
  const res = await fetch(`${base}/api/metrics`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${key}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ metrics }),
  });
  if (!res.ok) {
    const text = await res.text();
    throw new Error(`Push failed (${res.status}): ${text}`);
  }
  return res.json();
}
```

#### 12) Do/Don't

- Do: Keep `{Label}` concise and stable; use consistent units; guard against clock skew (>5 min future).
- Do: Validate protocol availability before creating; handle id conflicts gracefully.
- Don't: Change `protocolId` after launch; exceed 100 metrics per batch; log secrets.

These rules align with the Snippet public API: register a protocol, push metrics, and read them for widget display with consistent timestamp handling and flexible units.