# Contrato HTTP entre @veo/sdk y veo-backend

Versión: **v1**

Este documento es la fuente de verdad sobre el contrato HTTP. Cualquier
cambio debe reflejarse simultáneamente en ambos repos.

## Base URL

- Producción: `https://api.veo.io`
- Desarrollo: `http://localhost:3001`

## Versionado

Todos los endpoints viven bajo `/v1/`. Cambios incompatibles requieren
nueva versión (`/v2/`).

## Authentication

Todos los endpoints requieren API key del workspace:

- **Header:** `X-Api-Key: pk_xxx`
- **O query param:** `?key=pk_xxx` (necesario para `navigator.sendBeacon`)

Si la API key falta o es inválida: `401 Unauthorized`.

## Headers comunes

| Header | Requerido | Valor |
|--------|-----------|-------|
| `Content-Type` | Sí | `application/json` |
| `X-Api-Key` | Sí (o query) | `pk_xxx` |
| `X-Sdk-Version` | Recomendado | `0.0.1` |

## Endpoints

### POST /v1/identify

Crea o actualiza un end_user y opcionalmente su organization.

**Request body:**
```json
{
  "endUser": {
    "id": "user_123",
    "anonymousId": "anon_xyz",
    "traits": {
      "email": "user@example.com",
      "role": "admin",
      "plan": "free"
    }
  },
  "organization": {
    "id": "acme-corp",
    "attributes": {
      "plan": "pro",
      "mrr": 99.99,
      "industry": "saas"
    }
  }
}
```

**Response 200:**
```json
{
  "endUserId": "user_123",
  "organizationId": "acme-corp",
  "workspaceId": "uuid"
}
```

**Errors:**
- `400` - payload inválido
- `401` - API key inválida

---

### POST /v1/events

Ingesta batch de eventos (máx 100 por request).

**Request body:**
```json
{
  "events": [
    {
      "actionId": "01900000-0000-7000-8000-000000000001",
      "endUserId": "user_123",
      "organizationId": "acme-corp",
      "sessionId": "session_abc",
      "actionType": "track",
      "actionName": "project_created",
      "occurredAt": "2026-05-08T10:00:00.000Z",
      "pageUrl": "https://app.example.com/dashboard",
      "pagePath": "/dashboard",
      "pageTitle": "Dashboard",
      "pageReferrer": "https://google.com",
      "actionProperties": {
        "templateId": "blank"
      }
    }
  ]
}
```

**Action types válidos:**
- `pageview` - usuario visitó una página
- `track` - evento custom con nombre
- `identify` - usuario se identificó (también triggea upsert)
- `guide` - interacción con guía (futuro)

**Response 200:**
```json
{
  "accepted": 95,
  "deduped": 5
}
```

**Errors:**
- `400` - batch vacío, payload inválido, o > 100 eventos
- `401` - API key inválida
- `413` - body demasiado grande

---

## Idempotencia

Cada `actionId` es único. El backend mantiene ventana de 24h para
detectar duplicados. Si el SDK reintenta por timeout, el evento NO se
duplica.

## Reglas

- `occurredAt` siempre en ISO 8601 con timezone (Z o offset)
- `traits` y `attributes` son JSONB, sin schema fijo
- Máximo 64KB por campo JSONB
- Máximo 100 eventos por request
- Encoding: UTF-8

## SDK Version

El SDK debe enviar `X-Sdk-Version` en cada request para trazabilidad.
