Idioma: [English](./kydhub-docs.md) · **Español**

<!--
KydHub documentation Markdown source generated from the dev2 HTML preview.
Canonical review preview: http://192.168.0.232:4000/
Do not paste secrets, API keys, client secrets, webhook secrets, tokens, or PII here.
-->

# Qué resuelve KydHub

KydHub (Know Your Data Hub) es un hub de identidad y datos verificados. Permite que una empresa como **Acme** consulte datos autorizados de personas y empresas sin tener que recolectar, validar y mantener esa información por su cuenta.

La idea es simple: la persona o empresa mantiene sus datos en KydHub, KydHub actúa como fuente confiable, y cada integración recibe sólo los campos que necesita y que está autorizada a leer.

### Capacidades disponibles ahora

### Consultar una persona

Acme puede consultar datos autorizados de Bob Carter usando su KYD, por ejemplo `BOB.CARTER`, o un ID público `per_...`.

### Direcciones de persona

Una empresa puede pedir sólo las direcciones registradas que necesita, como dirección de envío, facturación o residencia, según autorización.

### Consultar una empresa

Acme puede consultar datos autorizados de otra empresa por KYD empresarial o ID público `comp_...`, sin usar KydHub como directorio abierto.

### Direcciones de empresa

También puede consultar direcciones o ubicaciones registradas de una empresa, cuando el caso de uso lo permite.

### Login con KWID opcional

Si querés delegar login o SSO, KydHub ofrece OAuth/OIDC. Si no, podés saltarlo y usar sólo Data API.

### Seguridad avanzada

API keys server-only, webhooks firmados y cifrado post-cuántico opcional para payloads sensibles.

### Integración por stack

Recetas para Python/FastAPI, Node.js/Express, Go y desktop apps como Tauri, Electron y Rust.

Ver recetas de integración

### Qué no hace la API pública

## Para empresas y usuarios

KydHub conecta dos necesidades: empresas que necesitan datos confiables y personas que quieren controlar qué información comparten.

#### Para empresas

- Consultás datos verificados sin volver a pedirlos manualmente.
- Pedís sólo los campos necesarios para tu caso de uso.
- Usás API keys server-to-server desde el backend de Acme.
- Podés conservar tu login actual y usar KydHub sólo como Data API.
- Si querés, también podés ofrecer Login con KWID/OIDC.
- Recibís eventos firmados por webhook cuando el flujo lo requiere.
#### Para personas

- Mantenés tu identidad y datos en un lugar controlado.
- Autorizás qué empresa puede leer qué información.
- Evitás repetir nombre, perfil o direcciones en cada integración.
- Las empresas leen datos autorizados, pero no modifican tus datos por API pública.
- Tu KYD, por ejemplo `BOB.CARTER`, funciona como identificador verificable.
### Servicios disponibles en esta etapa

| Servicio | Qué permite | Quién lo usa |
| --- | --- | --- |
| Persona | Consultar datos autorizados de una persona, como identidad o perfil básico. | Backend de Acme con API key. |
| Direcciones de persona | Consultar direcciones registradas de una persona cuando el flujo y los permisos lo permiten. | Backend de Acme con API key. |
| Empresa | Consultar datos autorizados de otra empresa usando KYD empresarial o ID público `comp_...`. | Backend de Acme con API key. |
| Direcciones de empresa | Consultar direcciones o ubicaciones registradas de una empresa. | Backend de Acme con API key. |

## Conceptos clave

Estos nombres aparecen en casi todos los ejemplos. La idea es que sepas qué identifica a quién antes de copiar una petición.

## Antes de empezar

Antes de hacer una llamada, separá tres cosas: el ambiente donde vas a probar, la credencial que autoriza a el backend de Acme y el identificador del recurso que querés consultar.

### Ambientes y URLs

| Qué | Valor | Cuándo se usa |
| --- | --- | --- |
| Dashboard dev | `https://dev.dashboard.kydhub.com` | Superficie browser del dashboard KydHub en dev. |
| Auth / endpoints OAuth dev | `https://dev.auth.kydhub.com` | Host canónico objetivo para endpoints OAuth/OIDC/Login. Hasta completar el rollout, leé los endpoints exactos desde discovery. |
| Docs dev | `https://dev.docs.kydhub.com` | Documentación developer en dev. |
| Base Data API | `https://dev.api.kydhub.com/api/v1` | Consultas server-to-server con API key. No requiere que uses OAuth de KydHub. |
| Issuer OIDC | `https://dev.kydhub.com` | Issuer estable. Validá que el `iss` del token coincida con este valor. |
| Discovery OIDC | `https://dev.kydhub.com/.well-known/openid-configuration` | Fuente de verdad para endpoints de authorize, token, UserInfo y JWKS. |
> Migración Auth Host: `https://dev.auth.kydhub.com` es el host objetivo para OAuth/OIDC/Login. `https://dev.kydhub.com` sigue siendo issuer/discovery estable; `https://dev.api.kydhub.com` sigue siendo el host de Data API. Los clientes deben usar discovery y no hardcodear endpoints OAuth.


### Autenticación mínima para Data API

Para consultar datos, Acme envía una API key desde su backend. Esa key identifica a la empresa y permite aplicar límites, scopes, auditoría y permisos.

```
QUERY https://dev.api.kydhub.com/api/v1/persons/query
X-API-KEY: kyd_sandbox_...
Content-Type: application/json
```

| Atributo | Qué es | Regla |
| --- | --- | --- |
| X-API-KEY | Secreto server-to-server de la empresa. | Nunca lo pongas en frontend, apps móviles, repositorios ni documentación pública. |
| Content-Type | Formato del cuerpo de la petición. | Usá `application/json` en ejemplos con body JSON. |

### Identificadores que vas a usar

| Atributo | Ejemplo | Cuándo usarlo |
| --- | --- | --- |
| person_kyd | `BOB.CARTER` | Cuando conocés el KYD/KWID público de la persona. |
| person_id | `per_...` | Cuando ya tenés el ID público opaco de esa persona. |
| company_kyd | `ACME.SUPPLIER` | Cuando consultás datos de una empresa por su KYD empresarial. |
| company_id | `comp_...` | Cuando ya tenés el ID público opaco de esa empresa. |
| fields | `["full_name", "addresses.city"]` | Lista de campos solicitados. Pedí sólo lo necesario para tu flujo. |
| service_name | `"acme-checkout"` | Slug técnico del servicio interno que hace la consulta. Debe ser minúscula ASCII con guiones. |
| response_profile | `"standard"` | Estructura del JSON completo: `standard`, `summary`, `compliance` o `audit`. |
| format | `{ "names": "surname_first_comma", "addresses": "postal" }` | Reglas de presentación de valores autorizados: nombres, direcciones, texto, fechas y locale. |

## Formatos de respuesta: perfil vs presentación

KydHub separa tres decisiones distintas para que la integración no sea ambigua:

| Atributo | Qué decide | Ejemplo |
| --- | --- | --- |
| `fields` | Qué datos pide Acme. | `name`, `addresses.shipping`, `profile.occupation` |
| `response_profile` | Cómo se organiza el JSON completo. | `standard`, `summary`, `compliance`, `audit` |
| `format` | Cómo se presentan los valores autorizados. | apellido primero, mayúsculas, sin tildes, dirección postal, fecha localizada |

`format` es presentación de datos. No concede permisos, no agrega campos y no reemplaza scopes ni grants. Si un campo no está autorizado, KydHub no lo devuelve aunque exista una regla de formato para ese campo.

### Perfiles de respuesta (`response_profile`)

Estos perfiles no cambian la capitalización ni el contenido de nombres/direcciones; sólo cambian la estructura general del payload.

| `response_profile` | Uso | Diferencia real |
| --- | --- | --- |
| `standard` | Integración normal, SDKs, storage y webhooks. | Devuelve los campos pedidos en estructura canónica bajo `fields`. |
| `summary` | UI, cards, checkout, soporte y listados. | Devuelve un resumen legible y menos profundo para mostrar rápido. |
| `compliance` | Onboarding, revisión formal, verificación y evidencias. | Agrupa datos formales/verificados y señales de compliance; no es “formato de texto”. |
| `audit` | Logs, debugging y evidencia de acceso. | Devuelve trazabilidad de request/autorización/resultado; no expande datos de negocio innecesarios. |

### Formato de presentación (`format`)

El cliente puede pedir que KydHub preformatee valores para no implementar esas reglas en cada aplicación.

```json
{
  "fields": ["name", "addresses.shipping", "profile.occupation", "created_at"],
  "response_profile": "standard",
  "format": {
    "names": "surname_first_comma",
    "addresses": "multiline",
    "text": "locale_title_case",
    "dates": "localized_datetime",
    "locale": "es-US",
    "include_raw": true
  }
}
```

### Respuesta cuando falta autorización

Si el servicio todavía no tiene un data grant aprobado, la Data API responde con un error accionable. No devuelve UUIDs internos ni usa el wrapper del portal.

```json
{
  "status": "error",
  "error": {
    "code": "data_grant_required",
    "person_kyd": "BOB.CARTER",
    "service_name": "acme-checkout",
    "requested_fields": ["name"],
    "required_permissions": [
      {"permission": "person.identity:read", "reason": "Required to identify the requested person in the Data API flow."},
      {"permission": "person.name:read", "reason": "Required to read the requested field: name."}
    ],
    "resolution": {
      "action": "request_data_grant",
      "endpoint": "POST /api/v1/data-grants/requests",
      "request_body": {
        "subject_type": "person",
        "subject_kyd": "BOB.CARTER",
        "requested_scopes": ["person.identity:read", "person.name:read"],
        "purpose": "Allow acme-checkout to read the person's name.",
        "external_reference": "acme-checkout"
      }
    }
  }
}
```

Cuando la respuesta es exitosa, `person_id` siempre usa prefijo `per_`; nunca se expone el UUID interno de la tabla persona.


Respuesta esperada:

```json
{
  "person_id": "per_7Q9M2K4R",
  "person_kyd": "BOB.CARTER",
  "response_profile_used": "standard",
  "format_used": {
    "names": "surname_first_comma",
    "addresses": "multiline",
    "text": "locale_title_case",
    "dates": "localized_datetime",
    "locale": "es-US",
    "include_raw": true
  },
  "fields": {
    "name": {
      "raw": "Bob José de la Cruz O'Neill García",
      "formatted": "O'Neill García, Bob José de la Cruz"
    },
    "addresses": {
      "shipping": {
        "raw": {
          "line1": "1200 Brickell Ave",
          "line2": "Suite 800",
          "city": "Miami",
          "region": "FL",
          "postal_code": "33131",
          "country": "US"
        },
        "formatted": "1200 Brickell Ave\nSuite 800\nMiami, FL 33131\nUS"
      }
    },
    "profile": {
      "occupation": {
        "raw": "software engineer",
        "formatted": "Software engineer"
      }
    },
    "created_at": {
      "raw": "2026-06-30T18:40:00Z",
      "formatted": "30/06/2026 18:40 UTC"
    }
  }
}
```

### Formatos propuestos para nombres

> **Granularidad del campo nombre:** `name` devuelve el nombre completo autorizado. `name.primary` devuelve primer nombre + primer apellido para UI compacta. `name.parts` devuelve arrays estructurados: `given_names`, `middle_names`, `family_names`. `format.names` controla presentación/orden/capitalización; no debe eliminar silenciosamente segundos nombres o segundos apellidos.


Dato base usado en los ejemplos:

```json
{
  "given_names": ["Bob", "José"],
  "middle_names": ["de la Cruz"],
  "family_names": ["O'Neill", "García"],
  "full_name": "Bob José de la Cruz O'Neill García"
}
```

| `format.names` | Salida `formatted` | Uso recomendado |
| --- | --- | --- |
| `as_recorded` | `Bob José de la Cruz O'Neill García` | Máxima fidelidad al dato guardado. |
| `given_first` | `Bob José de la Cruz O'Neill García` | UI normal orientada a usuario final. |
| `surname_first_comma` | `O'Neill García, Bob José de la Cruz` | CRM, reportes, listas administrativas. |
| `surname_first_space` | `O'Neill García Bob José de la Cruz` | Sistemas antiguos que no aceptan coma. |
| `locale_title_case` | `Bob José de la Cruz O'Neill García` | Capitalización respetando idioma/región. |
| `upper` | `BOB JOSÉ DE LA CRUZ O'NEILL GARCÍA` | Documentos o reportes en mayúsculas conservando tildes. |
| `lower` | `bob josé de la cruz o'neill garcía` | Normalización visual o matching blando. |
| `ascii` | `Bob Jose de la Cruz ONeill Garcia` | Sistemas sin Unicode o búsquedas simples. |
| `ascii_upper` | `BOB JOSE DE LA CRUZ ONEILL GARCIA` | Sistemas legacy/regulatorios que requieren ASCII mayúscula. |
| `initials` | `BJDCONG` | Avatares o vistas ultracompactas. |
| `initials_dotted` | `B. J. O. G.` | Avatares, firma compacta o UI. |

Ejemplo de request:

```json
{
  "fields": ["name"],
  "format": {
    "names": "ascii_upper",
    "include_raw": true
  }
}
```

Respuesta:

```json
{
  "name": {
    "raw": "Bob José de la Cruz O'Neill García",
    "formatted": "BOB JOSE DE LA CRUZ ONEILL GARCIA",
    "format_used": "ascii_upper"
  }
}
```

### Formatos propuestos para direcciones

Dato base usado en los ejemplos:

```json
{
  "line1": "1200 Brickell Ave",
  "line2": "Suite 800",
  "city": "Miami",
  "region": "FL",
  "postal_code": "33131",
  "country": "US"
}
```

| `format.addresses` | Salida `formatted` | Uso recomendado |
| --- | --- | --- |
| `as_recorded` | `1200 Brickell Ave, Suite 800, Miami, FL, 33131, US` | Fidelidad al registro original. |
| `single_line` | `1200 Brickell Ave, Suite 800, Miami, FL 33131, US` | Cards, checkout, dashboards y tablas. |
| `multiline` | `1200 Brickell Ave\nSuite 800\nMiami, FL 33131\nUS` | PDFs, documentos y pantallas con bloque postal. |
| `postal` | `1200 Brickell Ave\nSuite 800\nMiami FL 33131\nUNITED STATES` | Envíos físicos y logística según país. |
| `compact` | `Miami, FL, US` | Perfil, búsqueda o listado resumido. |
| `uppercase` | `1200 BRICKELL AVE, SUITE 800, MIAMI, FL 33131, US` | Etiquetas y sistemas que piden mayúscula. |
| `ascii_upper` | `AV. JOSE MARIA MORELOS #123, MERIDA, YUCATAN, MX` | Direcciones con tildes para sistemas legacy. |

Ejemplo con `postal`:

```json
{
  "address": {
    "formatted": "1200 Brickell Ave\nSuite 800\nMiami FL 33131\nUNITED STATES",
    "format_used": "postal",
    "country_format_used": "US"
  }
}
```

### Formatos propuestos para texto general y metadata

Aplica a campos como `profile.occupation`, `company.display_name`, `industry`, `city`, `region` o labels visibles.

Dato base:

```text
Inteligencia Artificial & Automatización
```

| `format.text` | Salida `formatted` | Uso recomendado |
| --- | --- | --- |
| `preserve` | `Inteligencia Artificial & Automatización` | Mantener valor original. |
| `upper` | `INTELIGENCIA ARTIFICIAL & AUTOMATIZACIÓN` | Reportes o UI en mayúscula. |
| `lower` | `inteligencia artificial & automatización` | Normalización visual. |
| `locale_title_case` | `Inteligencia artificial & automatización` | Títulos legibles respetando idioma. |
| `ascii` | `Inteligencia Artificial & Automatizacion` | Sistemas sin soporte completo Unicode. |
| `ascii_upper` | `INTELIGENCIA ARTIFICIAL & AUTOMATIZACION` | Legacy, matching o exportaciones rígidas. |
| `slug` | `inteligencia-artificial-automatizacion` | URLs, keys, anchors o integraciones internas. |

### Formatos propuestos para fechas

Dato base:

```text
2026-06-30T18:40:00Z
```

| `format.dates` | Salida `formatted` | Uso recomendado |
| --- | --- | --- |
| `iso` | `2026-06-30T18:40:00Z` | APIs, storage e interoperabilidad. |
| `date` | `2026-06-30` | Reportes por día. |
| `datetime` | `2026-06-30 18:40:00 UTC` | Logs legibles. |
| `localized_date` | `30/06/2026` | UI localizada. |
| `localized_datetime` | `30/06/2026 18:40 UTC` | UI localizada con hora. |

### Error por formato inválido

```json
{
  "error": "invalid_format",
  "message": "Unsupported presentation format.",
  "field": "format.names",
  "allowed_formats": [
    "as_recorded",
    "given_first",
    "surname_first_comma",
    "surname_first_space",
    "locale_title_case",
    "upper",
    "lower",
    "ascii",
    "ascii_upper",
    "initials",
    "initials_dotted"
  ]
}
```

## API keys y credenciales

Para usar la Data API, Acme necesita una API key creada desde el portal de KydHub. Esa key identifica a la empresa que consulta, aplica límites de uso y permite auditar qué campos se solicitaron.

### Dónde conseguir una API key

| Paso | Qué hacer | Resultado |
| --- | --- | --- |
| 1 | Entrá al portal de KydHub con tu usuario. | Accedés a tu espacio personal o empresarial. |
| 2 | Cambiá al contexto de empresa, por ejemplo `Acme`. | Las credenciales quedan asociadas a esa empresa. |
| 3 | Abrí `Company → API Access → API keys`. | Ves las API keys activas, revocadas y sus fechas. |
| 4 | Creá una nueva API key y elegí si expira o nunca caduca. | KydHub genera el secreto completo una sola vez. |
| 5 | Copiá el secreto y guardalo en el backend de Acme o secret manager. | Ya podés llamar la Data API desde tu servidor. |

### Cómo usar la API key

La API key viaja en el header `X-API-KEY`. No identifica a una persona final; identifica a la empresa integradora que está consultando datos autorizados.

```
curl -X QUERY https://dev.api.kydhub.com/api/v1/persons/query \
  -H "X-API-KEY: $KYDHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"person_kyd":"BOB.CARTER","fields":["full_name"]}'
```

| Atributo | Qué significa | Regla de uso |
| --- | --- | --- |
| $KYDHUB_API_KEY | Variable de entorno con el secreto real de Acme. | No pegues el valor real en código, logs, tickets ni documentación. |
| X-API-KEY | Header de autenticación server-to-server. | Debe enviarlo el backend de Acme, nunca el navegador. |
| person_kyd | KYD público de la persona consultada. | Puede cambiarse por `person_id` si ya lo tenés. |
| fields | Campos exactos que querés leer. | Pedí lo mínimo necesario para tu caso de uso. |

### Diferencia entre credenciales

| Credencial | Para qué sirve | Dónde debe vivir | ¿Puede ir al browser? |
| --- | --- | --- | --- |
| API key | Consultar Data API desde backend. | Backend o secret manager. | No. |
| OAuth client_id | Iniciar Login con KWID/OIDC. | Frontend/backend según flujo. | Sí, no es secreto. |
| OAuth client_secret | Autenticar un cliente OAuth confidencial. | Backend o secret manager. | No. |
| Webhook secret | Verificar firmas de eventos entrantes. | Backend receptor de webhooks. | No. |

### Límites Company Free

| Área | Límite | Qué implica |
| --- | --- | --- |
| API keys | 2 activas | Podés tener dos keys activas por empresa; revocá una antes de crear una tercera. |
| Expiración | Opcional | Una key puede no expirar o tener fecha de vencimiento. |
| Rate limit | 60/min | Si superás el límite, la API responde con error de rate limit. |
| Uso diario | 2.000/día | Diseñado para integraciones iniciales y pilotos controlados. |

## Integración por stack

Estas recetas muestran cómo conectar el backend o la app de Acme con KydHub. La regla principal es separar bien los tipos de integración: la **Data API** usa API key server-to-server; **OAuth/OIDC** es opcional para Login con KWID; las apps de escritorio deben usar navegador del sistema + PKCE y nunca distribuir secretos.

### Qué patrón usar según tu stack

| Stack | Uso recomendado | Secreto permitido | Notas |
| --- | --- | --- | --- |
| Python / FastAPI | Backend server-to-server para Data API, OAuth callback y webhooks. | Sí, en variables de entorno/secret manager. | Ideal para validar tokens, guardar API keys y verificar HMAC. |
| Node.js / Express | Backend server-to-server para Data API, OAuth callback y webhooks. | Sí, en variables de entorno/secret manager. | Buen punto de entrada para apps web SPA/SSR. |
| Go | Backend o servicio interno de alto rendimiento. | Sí, en variables de entorno/secret manager. | Útil para integraciones B2B, workers y servicios empresariales. |
| Tauri | Desktop app con navegador del sistema + PKCE. | No dentro del binario. | Usá callback custom scheme o loopback local; Data API sensible vía backend. |
| Electron | Desktop app con navegador del sistema + PKCE. | No dentro del paquete de la app. | No uses `client_secret` en main/renderer. |
| Rust | CLI/desktop/backend con PKCE o servicio server-side. | Depende del tipo de app. | Servidor Rust puede guardar secrets; app distribuida no. |

### Python / FastAPI · consultar persona

Este ejemplo expone un endpoint interno de Acme que consulta datos autorizados de Bob Carter en KydHub. La API key vive sólo en el backend.

```
import os
import httpx
from fastapi import FastAPI, HTTPException

app = FastAPI()
KYDHUB_BASE_URL = os.getenv("KYDHUB_BASE_URL", "https://dev.api.kydhub.com/api/v1")
api_key = os.getenv("KYDHUB_API_KEY", "set-in-secret-manager")

@app.get("/internal/kydhub/person/{person_kyd}")
async def get_person(person_kyd: str):
    payload = {
        "person_kyd": person_kyd,
        "service_name": "acme-checkout",
        "fields": ["full_name", "profile.display_name"],
    }
    headers = {
        "X-API-KEY": api_key,
        "Content-Type": "application/json",
    }
    async with httpx.AsyncClient(timeout=10) as client:
        response = await client.request("QUERY", f"{KYDHUB_BASE_URL}/api/v1/persons/query", json=payload, headers=headers)
    if response.status_code >= 400:
        raise HTTPException(status_code=response.status_code, detail=response.json())
    return response.json()
```

### Node.js / Express · consultar empresa

Este ejemplo consulta datos autorizados de una empresa por `company_kyd`. La API key queda en el backend Express.

```
import express from "express";

const app = express();
app.use(express.json());

const KYDHUB_BASE_URL = process.env.KYDHUB_BASE_URL ?? "https://dev.api.kydhub.com/api/v1";
const apiKey = process.env.KYDHUB_API_KEY ?? "set-in-secret-manager";

app.post("/internal/kydhub/company", async (req, res) => {
  const response = await fetch(`${KYDHUB_BASE_URL}/api/v1/companies/query`, {
    method: "QUERY",
    headers: {
      "X-API-KEY": apiKey,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      company_kyd: req.body.company_kyd,
      service_name: "acme-supplier-review",
      fields: ["legal_name", "status", "profile.industry"]
    })
  });

  const body = await response.json();
  res.status(response.status).json(body);
});
```

### Go · consultar direcciones de empresa

Go funciona muy bien para servicios internos, workers o integraciones B2B. Este ejemplo consulta ubicaciones de una empresa usando `company_kyd`.

```
package main

import (
  "bytes"
  "encoding/json"
  "net/http"
  "os"
  "time"
)

func queryCompanyLocations(companyKYD string) (*http.Response, error) {
  baseURL := os.Getenv("KYDHUB_BASE_URL")
  if baseURL == "" { baseURL = "https://dev.api.kydhub.com/api/v1" }

  payload := map[string]any{
    "company_kyd": companyKYD,
    "service_name": "acme-supplier-review",
    "fields": []string{"locations.city", "locations.country"},
  }
  body, err := json.Marshal(payload)
  if err != nil { return nil, err }

  req, err := http.NewRequest("QUERY", baseURL+"/api/v1/companies/query", bytes.NewReader(body))
  if err != nil { return nil, err }
  req.Header.Set("X-API-KEY", os.Getenv("KYDHUB_API_KEY"))
  req.Header.Set("Content-Type", "application/json")

  client := &http.Client{Timeout: 10 * time.Second}
  return client.Do(req)
}
```

### Desktop apps · Tauri, Electron y Rust

Las apps de escritorio distribuidas no deben incluir secretos. Para Login con KWID usá navegador del sistema, PKCE, `state` y `nonce`. Para Data API sensible, la app debe llamar a tu backend y tu backend llama a KydHub.

| Opción | Uso | Redirect URI | Secreto |
| --- | --- | --- | --- |
| Custom scheme | App registrada como handler. | `acme://oauth/callback` | No usar `client_secret`. |
| Loopback local | App levanta callback temporal. | `http://127.0.0.1:{port}/callback` | No usar `client_secret`. |
| Backend broker | Desktop habla con backend de Acme. | Callback web de Acme. | El backend sí puede guardar secretos. |

### Electron · abrir Login con KWID

```
import { shell } from "electron";
import crypto from "node:crypto";

const state = crypto.randomUUID();
const nonce = crypto.randomUUID();
const codeVerifier = crypto.randomBytes(32).toString("base64url");
const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");

const url = new URL("https://dev.auth.kydhub.com/oauth/authorize");
url.searchParams.set("client_id", "acme_desktop_client");
url.searchParams.set("redirect_uri", "acme://oauth/callback");
url.searchParams.set("response_type", "code");
url.searchParams.set("scope", "openid kwid profile");
url.searchParams.set("state", state);
url.searchParams.set("nonce", nonce);
url.searchParams.set("code_challenge", codeChallenge);
url.searchParams.set("code_challenge_method", "S256");

shell.openExternal(url.toString());
```

### Tauri / Rust · abrir navegador del sistema

```
use open;
use url::Url;

fn start_kydhub_login(code_challenge: &str, state: &str, nonce: &str) -> anyhow::Result<()> {
    let mut url = Url::parse("https://dev.auth.kydhub.com/oauth/authorize")?;
    url.query_pairs_mut()
        .append_pair("client_id", "acme_desktop_client")
        .append_pair("redirect_uri", "acme://oauth/callback")
        .append_pair("response_type", "code")
        .append_pair("scope", "openid kwid profile")
        .append_pair("state", state)
        .append_pair("nonce", nonce)
        .append_pair("code_challenge", code_challenge)
        .append_pair("code_challenge_method", "S256");

    open::that(url.as_str())?;
    Ok(())
}
```


## HTTP QUERY (RFC 10008) y compatibilidad

`QUERY` es canónico para consultas complejas read-only de KydHub. Es seguro, idempotente y reintentable. El contenido JSON de consulta no modifica el estado de personas ni empresas.

| Contrato | Valor |
| --- | --- |
| Operaciones canónicas | `QUERY /api/v1/persons/query` y `QUERY /api/v1/companies/query` |
| Headers requeridos | `X-API-KEY`, `Content-Type: application/json`; usa `Accept: application/json` para negociación explícita. |
| Discovery | `OPTIONS` devuelve `Allow: QUERY, POST, OPTIONS` y `Accept-Query: application/json`. |
| Límite del body | 1 MiB; el contenido excedido devuelve `413`. |
| Política privada de caché | `Cache-Control: no-store, private`; el rollout inicial deshabilita caché compartida. |
| Compatibilidad | `POST /api/v1/*/query` es un alias temporal deprecado con el mismo pipeline de autorización/consulta hasta que Gate C pruebe soporte en clientes, proxy, WAF y gateway. |

Errores: `400` metadatos/JSON ausentes o malformados; `401` API key ausente/inválida; `403` autorización denegada; `406` representación no aceptable; `413` body excedido; `415` media type no soportado; `422` contenido no procesable.

> Usa el alias POST solo cuando un cliente o intermediario desplegado no pueda emitir o reenviar QUERY. La preparación para producción requiere evidencia end-to-end de Gate C.

## QUERY · Consultar datos de una persona

Este es el primer caso de uso principal de la Data API: una empresa como Acme consulta datos autorizados de una persona, por ejemplo Bob Carter, usando su `person_kyd` o su `person_id`.

### Petición mínima

```
QUERY https://dev.api.kydhub.com/api/v1/persons/query
X-API-KEY: $KYDHUB_API_KEY
Content-Type: application/json

{
  "person_kyd": "BOB.CARTER",
  "service_name": "acme-checkout",
  "fields": ["full_name", "profile.display_name"]
}
```

### Atributos de la petición

| Atributo | Obligatorio | Qué significa | Regla |
| --- | --- | --- | --- |
| X-API-KEY | Sí | API key server-to-server de Acme. | Debe vivir sólo en backend/secret manager. |
| person_kyd | Uno de dos | KYD/KWID público de la persona, como `BOB.CARTER`. | Usalo cuando el usuario o tu sistema conocen el alias público. |
| person_id | Uno de dos | ID público opaco de la persona, como `per_...`. | Usalo cuando ya lo recibiste antes de KydHub. No envíes UUIDs internos. |
| service_name | Sí | Slug técnico del servicio que hace la consulta. | Formato obligatorio: `^[a-z0-9]+(?:-[a-z0-9]+)*$`. Válidos: `acme-checkout`, `billing-v2`. Inválidos: `Acme Checkout`, `acme_checkout`, `acmé-checkout`, `acme--checkout`. |
| fields | Sí | Lista exacta de campos que Acme quiere leer. | Pedí sólo lo necesario. Cada campo puede requerir scope/grant. |
| response_profile | Opcional | Estructura del payload completo. | Usá `standard`, `summary`, `compliance` o `audit`. Omitilo para `standard`. |
| format | Opcional | Presentación de valores autorizados. | Configura nombres, direcciones, texto, fechas, locale e `include_raw`; no agrega permisos. |

### Respuesta esperada

```
{
  "data": {
    "person_id": "per_7Q9M2K4R",
    "person_kyd": "BOB.CARTER",
    "fields": {
      "full_name": "Bob Carter",
      "profile": {
        "display_name": "Bob Carter"
      }
    },
    "grant": {
      "status": "active",
      "scopes": ["person.identity:read", "person.profile:read"]
    }
  }
}
```

### Atributos de la respuesta

| Atributo | Qué significa | Cómo usarlo |
| --- | --- | --- |
| data | Contenedor principal de la respuesta. | Leé los datos desde este objeto; no asumas campos fuera del contrato. |
| person_id | ID público opaco de Bob en KydHub. | Guardalo para futuras consultas en vez de depender sólo del alias. |
| person_kyd | KYD/KWID público normalizado. | Mostralo si tu UI necesita referirse a la identidad pública. |
| fields | Objeto con los campos aprobados y devueltos. | No esperes campos que no pediste o que no fueron autorizados. |
| grant.status | Estado de la autorización usada para responder. | `active` indica que la lectura fue autorizada. |
| grant.scopes | Scopes de lectura que respaldan la respuesta. | Útil para auditoría y debugging de permisos. |

### Consultar por `person_id`

Si Acme ya tiene el ID público opaco de Bob, puede usar `person_id` en lugar de `person_kyd`.

```
{
  "person_id": "per_7Q9M2K4R",
  "service_name": "acme-checkout",
  "fields": ["full_name"]
}
```

### Errores comunes

| Error | Qué significa | Qué hacer |
| --- | --- | --- |
| missing_api_key | No llegó `X-API-KEY`. | Enviá la key desde backend, nunca desde browser. |
| invalid_api_key | La key no existe, fue revocada o expiró. | Creá/rotá una key en el portal de KydHub. |
| grant_required | No hay autorización activa para leer esos datos. | Iniciá el flujo de autorización/consentimiento correspondiente. |
| field_not_allowed | Pediste un campo fuera de scopes o grant. | Quitá el campo o solicitá el permiso adecuado. |
| person_not_found | No existe una persona con ese `person_kyd` o `person_id`. | Verificá el identificador y no reintentes con datos inventados. |

## QUERY · Consultar direcciones de una persona

Una empresa puede consultar direcciones registradas de una persona cuando el caso de uso lo justifica y existe autorización para esos campos. Por ejemplo, Acme puede necesitar la ciudad y país de Bob Carter para completar un flujo de envío o facturación.

### Petición por KYD de persona

```
QUERY https://dev.api.kydhub.com/api/v1/persons/query
X-API-KEY: $KYDHUB_API_KEY
Content-Type: application/json

{
  "person_kyd": "BOB.CARTER",
  "service_name": "acme-shipping",
  "fields": [
    "addresses.type",
    "addresses.city",
    "addresses.region",
    "addresses.country",
    "addresses.postal_code"
  ]
}
```

### Atributos de la petición

| Atributo | Obligatorio | Qué significa | Regla |
| --- | --- | --- | --- |
| person_kyd | Uno de dos | KYD/KWID público de la persona, como `BOB.CARTER`. | Usalo si conocés el alias público de la persona. |
| person_id | Uno de dos | ID público opaco de la persona, como `per_...`. | Usalo si ya fue devuelto por KydHub en una consulta previa. |
| service_name | Sí | Slug técnico del servicio que pide la dirección. | Ejemplo: `acme-shipping` o `acme-billing`. |
| fields | Sí | Campos de dirección que querés leer. | Debe incluir sólo atributos necesarios y autorizados. |

### Campos de dirección habituales

| Campo | Qué devuelve | Cuándo pedirlo |
| --- | --- | --- |
| addresses.type | Tipo de dirección, por ejemplo `shipping`, `billing` o `registered`. | Cuando necesitás distinguir uso de la dirección. |
| addresses.line1 | Línea principal de la dirección. | Sólo si necesitás dirección completa para operar. |
| addresses.line2 | Complemento, departamento, piso o referencia. | Opcional; pedilo sólo si tu flujo lo usa. |
| addresses.city | Ciudad o localidad. | Útil para envío, cobertura o validación regional. |
| addresses.region | Provincia, estado o región. | Útil para impuestos, logística o reglas locales. |
| addresses.country | País en formato legible o código según contrato final. | Útil para cobertura, cumplimiento y facturación. |
| addresses.postal_code | Código postal. | Útil para envío, impuestos o validación territorial. |
| addresses.is_primary | Indica si es la dirección principal. | Pedilo si necesitás elegir una dirección por defecto. |

### Respuesta esperada

```
{
  "data": {
    "person_id": "per_7Q9M2K4R",
    "person_kyd": "BOB.CARTER",
    "fields": {
      "addresses": [
        {
          "type": "shipping",
          "city": "Buenos Aires",
          "region": "Ciudad Autónoma de Buenos Aires",
          "country": "AR",
          "postal_code": "C1000"
        }
      ]
    },
    "grant": {
      "status": "active",
      "scopes": ["person.addresses:read"]
    }
  }
}
```

### Atributos de la respuesta

| Atributo | Qué significa | Cómo usarlo |
| --- | --- | --- |
| fields.addresses | Lista de direcciones autorizadas y devueltas. | Puede venir vacía si no hay direcciones autorizadas o registradas para el filtro/campos pedidos. |
| type | Uso de la dirección. | No asumas que siempre existe una dirección de cada tipo. |
| city / region / country | Ubicación general. | Útil para validación, cobertura y reglas regionales. |
| postal_code | Código postal. | Puede estar ausente si no fue pedido, no existe o no fue autorizado. |
| grant.scopes | Scopes que respaldan la lectura. | Para direcciones de persona debe existir permiso de lectura de direcciones. |

### Variante por `person_id`

Si Acme ya guardó el `person_id`, puede consultar direcciones sin volver a enviar `person_kyd`.

```
{
  "person_id": "per_7Q9M2K4R",
  "service_name": "acme-shipping",
  "fields": ["addresses.city", "addresses.country"]
}
```

### Errores comunes

| Error | Qué significa | Qué hacer |
| --- | --- | --- |
| grant_required | Acme no tiene autorización activa para leer direcciones de Bob. | Pedí consentimiento o ajustá el flujo para no requerir dirección. |
| field_not_allowed | Algún campo `addresses.*` no está cubierto por scopes/grant. | Quitá el campo o solicitá el permiso correcto. |
| address_not_available | No hay dirección registrada/autorizada para los campos pedidos. | Mostrá una alternativa en tu flujo; no inventes dirección. |
| person_not_found | El `person_kyd` o `person_id` no existe. | Verificá el identificador recibido. |

## QUERY · Consultar datos de otra empresa

Una empresa como Acme puede consultar datos autorizados de otra empresa registrada en KydHub usando su `company_kyd` o su `company_id`. Este flujo sirve para validar proveedores, contrapartes, comercios, clientes empresa o relaciones B2B.

### Petición por `company_kyd`

```
QUERY https://dev.api.kydhub.com/api/v1/companies/query
X-API-KEY: $KYDHUB_API_KEY
Content-Type: application/json

{
  "company_kyd": "ACME.SUPPLIER",
  "service_name": "acme-supplier-review",
  "fields": [
    "legal_name",
    "display_name",
    "status",
    "profile.industry"
  ]
}
```

### Atributos de la petición

| Atributo | Obligatorio | Qué significa | Regla |
| --- | --- | --- | --- |
| X-API-KEY | Sí | API key server-to-server de Acme. | Identifica a la empresa que consulta y aplica límites/auditoría. |
| company_kyd | Uno de dos | KYD público de la empresa consultada, por ejemplo `ACME.SUPPLIER`. | Usalo cuando conocés el identificador KYD de la empresa objetivo. |
| company_id | Uno de dos | ID público opaco de empresa, por ejemplo `comp_...`. | Usalo cuando KydHub ya te devolvió ese ID en una integración previa. |
| service_name | Sí | Slug técnico del servicio que consulta. | Ejemplo: `acme-supplier-review`. |
| fields | Sí | Lista exacta de campos empresariales a leer. | Pedí sólo los campos necesarios. No hay respuesta completa por defecto. |

### Campos empresariales habituales

| Campo | Qué devuelve | Cuándo pedirlo |
| --- | --- | --- |
| legal_name | Nombre legal registrado de la empresa. | Validación contractual, facturación o onboarding B2B. |
| display_name | Nombre comercial o de visualización. | Mostrar la empresa en UI o reportes. |
| company_kyd | KYD empresarial normalizado. | Referencias cruzadas y futuras consultas. |
| company_id | ID público opaco `comp_...`. | Guardar un identificador estable sin usar UUID interno. |
| status | Estado público/autorizado de la empresa en KydHub. | Validar si la empresa está registrada, activa o verificable según contrato. |
| profile.industry | Industria o rubro si está disponible/autorizado. | Segmentación, compliance o clasificación de proveedores. |
| profile.website | Sitio web público si está disponible/autorizado. | Validación manual o presentación en UI. |

### Respuesta esperada

```
{
  "data": {
    "company_id": "comp_42P7K9M2",
    "company_kyd": "ACME.SUPPLIER",
    "fields": {
      "legal_name": "Acme Supplier LLC",
      "display_name": "Acme Supplier",
      "status": "active",
      "profile": {
        "industry": "Logistics"
      }
    },
    "scopes": ["company.identity:read", "company.profile:read"]
  }
}
```

### Atributos de la respuesta

| Atributo | Qué significa | Cómo usarlo |
| --- | --- | --- |
| company_id | ID público opaco de la empresa consultada. | Guardalo para futuras consultas. No lo confundas con UUID interno. |
| company_kyd | KYD empresarial normalizado. | Mostralo o úsalo como identificador público legible. |
| fields | Campos autorizados que KydHub devuelve. | No asumas campos no solicitados o no permitidos. |
| status | Estado autorizado de la empresa en KydHub. | Usalo para decisiones de UI/flujo, no como sustituto de revisión legal si aplica. |
| scopes | Scopes que respaldan la lectura. | Útil para auditoría y debugging de permisos. |

### Variante por `company_id`

Si Acme ya tiene el ID público opaco de la empresa, puede usar `company_id` en lugar de `company_kyd`.

```
{
  "company_id": "comp_42P7K9M2",
  "service_name": "acme-supplier-review",
  "fields": ["legal_name", "status"]
}
```

### Errores comunes

| Error | Qué significa | Qué hacer |
| --- | --- | --- |
| company_not_found | No existe una empresa con ese `company_kyd` o `company_id`. | Verificá el identificador recibido. No intentes descubrir empresas por fuerza bruta. |
| field_not_allowed | Pediste un campo empresarial no permitido para tu key/scope. | Quitá el campo o pedí el scope correspondiente desde el portal/contrato. |
| company_scope_required | La API key no tiene el scope de datos de empresa requerido. | Solicitá/habilitá el scope necesario; los planes limitan capacidad, no la categoría de dato. |
| invalid_api_key | La API key no existe, fue revocada o expiró. | Rotá o creá una nueva API key desde el portal. |

## QUERY · Consultar direcciones de una empresa

Acme también puede consultar direcciones o ubicaciones registradas de otra empresa cuando necesita validar datos de facturación, operación, sucursales o cobertura. Igual que el resto de la Data API, la consulta es de sólo lectura y requiere pedir campos concretos.

### Petición por `company_kyd`

```
QUERY https://dev.api.kydhub.com/api/v1/companies/query
X-API-KEY: $KYDHUB_API_KEY
Content-Type: application/json

{
  "company_kyd": "ACME.SUPPLIER",
  "service_name": "acme-supplier-review",
  "fields": [
    "locations.type",
    "locations.name",
    "locations.city",
    "locations.region",
    "locations.country"
  ]
}
```

### Atributos de la petición

| Atributo | Obligatorio | Qué significa | Regla |
| --- | --- | --- | --- |
| company_kyd | Uno de dos | KYD público de la empresa consultada. | Usalo cuando conocés el identificador KYD empresarial. |
| company_id | Uno de dos | ID público opaco de la empresa, como `comp_...`. | Usalo si KydHub ya lo devolvió antes. |
| service_name | Sí | Servicio que consulta las ubicaciones. | Ejemplo: `acme-supplier-review`. |
| fields | Sí | Campos de ubicación/dirección empresarial solicitados. | Pedí sólo lo necesario para tu flujo B2B. |

### Campos de ubicación empresarial habituales

| Campo | Qué devuelve | Cuándo pedirlo |
| --- | --- | --- |
| locations.type | Tipo de ubicación, por ejemplo `headquarters`, `branch`, `billing` o `operations`. | Cuando necesitás distinguir casa matriz, sucursal u operación. |
| locations.name | Nombre legible de la ubicación. | Útil para mostrarla en UI o reportes. |
| locations.line1 | Línea principal de dirección empresarial. | Sólo si necesitás dirección completa. |
| locations.line2 | Complemento o referencia. | Opcional; pedilo sólo si tu operación lo usa. |
| locations.city | Ciudad o localidad. | Cobertura, logística o validación regional. |
| locations.region | Provincia, estado o región. | Reglas fiscales, legales o logísticas. |
| locations.country | País. | Cobertura, compliance o facturación internacional. |
| locations.postal_code | Código postal. | Facturación, impuestos o envío físico. |
| locations.is_primary | Indica si es la ubicación principal. | Para elegir ubicación por defecto. |

### Respuesta esperada

```
{
  "data": {
    "company_id": "comp_42P7K9M2",
    "company_kyd": "ACME.SUPPLIER",
    "fields": {
      "locations": [
        {
          "type": "headquarters",
          "name": "Main office",
          "city": "Miami",
          "region": "Florida",
          "country": "US",
          "is_primary": true
        }
      ]
    },
    "scopes": ["company.locations:read"]
  }
}
```

### Atributos de la respuesta

| Atributo | Qué significa | Cómo usarlo |
| --- | --- | --- |
| fields.locations | Lista de ubicaciones o direcciones empresariales autorizadas. | Puede venir vacía si no hay ubicaciones disponibles/autorizadas. |
| type | Uso o categoría de la ubicación. | No asumas que todas las empresas tienen los mismos tipos. |
| name | Nombre legible de la ubicación. | Usalo para UI/reportes, no como ID único. |
| city / region / country | Ubicación geográfica general. | Útil para reglas regionales y validación de cobertura. |
| is_primary | Marca ubicación principal. | Puede faltar si no fue solicitado o no está definido. |
| scopes | Scopes que respaldan la lectura. | Para ubicaciones debe existir permiso de lectura de ubicaciones empresariales. |

### Variante por `company_id`

```
{
  "company_id": "comp_42P7K9M2",
  "service_name": "acme-supplier-review",
  "fields": ["locations.city", "locations.country"]
}
```

### Errores comunes

| Error | Qué significa | Qué hacer |
| --- | --- | --- |
| company_not_found | No existe empresa con ese `company_kyd` o `company_id`. | Verificá el identificador por un canal autorizado. |
| company_locations_scope_required | La API key no tiene permiso para leer ubicaciones de empresa. | Solicitá/habilitá `company.locations:read`; los planes limitan capacidad, no la categoría de dato. |
| location_not_available | No hay ubicación disponible/autorizada para los campos pedidos. | Mostrá una alternativa o pedí menos campos. |
| field_not_allowed | Algún campo `locations.*` no está permitido. | Quitá el campo o pedí el scope correspondiente. |

## Login con KWID / OAuth OIDC opcional

Login con KWID permite que Acme use KydHub como proveedor de identidad mediante OAuth 2.0 y OpenID Connect. Es útil cuando querés delegar login, SSO o verificación de identidad en KydHub.

### Cuándo usarlo y cuándo saltarlo

| Situación | Recomendación | Por qué |
| --- | --- | --- |
| Acme ya tiene login propio | Podés saltar OAuth de KydHub. | Usá Data API con API key desde backend para consultar datos autorizados. |
| Acme quiere Login con KWID | Usá OAuth 2.0 + OIDC. | KydHub autentica al usuario y devuelve tokens/claims verificables. |
| Aplicación web con backend | Usá Authorization Code + PKCE y cliente confidencial si aplica. | El backend puede guardar secretos de forma segura. |
| Aplicación móvil o escritorio | Usá PKCE y tratala como cliente público salvo que haya backend. | No pongas `client_secret` dentro de una app distribuida. |

## GET1 · Iniciar Login con KWID

**Para qué sirve:** Acme redirige al usuario a KydHub para que confirme su identidad con KWID. Cuando vuelve al callback de Acme, el backend recibe un `code` que luego canjea por tokens.

```
# redirigí al usuario a KydHub
GET https://dev.auth.kydhub.com/oauth/authorize
  ?client_id=kh_client_…
  &redirect_uri=https://app.acme.example/callback
  &response_type=code
  &scope=openid kwid email profile company:read
  &state=RANDOM
  &code_challenge=…&code_challenge_method=S256
```

| Parámetro | ¿Obligatorio? | Qué es |
| --- | --- | --- |
| client_id | Sí | El ID de la app de Acme en KydHub. |
| redirect_uri | Sí | A dónde vuelve el usuario. Debe coincidir **exacto** con el registrado. |
| response_type | Sí | Siempre `code`. |
| scope | Sí | Datos que pedís, separados por espacio. |
| state | Sí | Valor random anti-CSRF; lo validás al volver. |
| code_challenge | Sí | PKCE: el hash de tu `code_verifier` (método S256). |
| nonce | Opcional | Recomendado: lo validás en el id_token para evitar replays. |
| login_hint | Opcional | Pre-llená el KWID (ej. `BOB.CARTER`). |
| prompt | Opcional | `login` fuerza re-autenticación aunque haya SSO. |

## POST2 · Verificación por email

Si el usuario no tiene una sesión activa en KydHub, el primer `/authorize` puede volver con `error=login_required` y `verification_required`. **Esto es un estado intermedio esperado, no un error fatal.** Acme debe mostrar “Revisá tu email” y arrancar la verificación desde su backend.

```
POST https://dev.auth.kydhub.com/oauth/login/start
Content-Type: application/json

{
  "client_id": "kh_client_…",
  "redirect_uri": "https://app.acme.example/callback",
  "scope": "openid kwid email profile company:read",
  "state": "RANDOM",
  "code_challenge": "…",
  "code_challenge_method": "S256",
  "verification_identifier": "BOB.CARTER"
}
```

| Campo | ¿Obligatorio? | Qué es |
| --- | --- | --- |
| verification_identifier | Sí | El KWID/KYDid. Acá **no** va `login_hint`. |
| client_id | Sí | El ID de tu app. |
| redirect_uri | Sí | El mismo del paso 1, exacto. |
| scope | Sí | String con espacios, no array. |
| state | Sí | Valor anti-CSRF del paso 1. |
| code_challenge | Sí | PKCE del paso 1. |
| code_challenge_method | Sí | `S256`. |
| nonce | Opcional | Recomendado; validalo en el id_token. |

La respuesta viene envuelta en `data`:

```
{
  "data": {
    "status": "pending_verification",
    "login_attempt_id": "login_attempt_…",
    "expires_at": "2026-06-19T15:30:00Z",  // TTL 15 min
    "challenge_prefix": "abc12345"     // solo diagnóstico
  }
}
```

## POST3 · Canjear el code por el token

**Para qué sirve:** cuando KydHub te devuelve al `redirect_uri` con un `code`, tu **backend** lo cambia por los tokens. Acá sí entra el `client_secret`.

```
POST https://dev.auth.kydhub.com/oauth/token
Content-Type: application/json

{
  "grant_type": "authorization_code",
  "client_id": "kh_client_…",
  "client_secret": "CLIENT_SECRET_DEL_BACKEND",
  "code": "CODE_DEL_CALLBACK",
  "redirect_uri": "https://app.acme.example/callback",
  "code_verifier": "EL_VERIFIER_ORIGINAL"
}
```

| Campo | ¿Obligatorio? | Qué es |
| --- | --- | --- |
| grant_type | Sí | Siempre `authorization_code`. |
| client_id | Sí | El ID de tu app. |
| client_secret | Sí | Secreto del backend. Nunca en el cliente. |
| code | Sí | El código que llegó al callback (un solo uso). |
| redirect_uri | Sí | El mismo del paso 1. |
| code_verifier | Sí | El PKCE original (el que generó el `code_challenge`). |

Respuesta (objeto OAuth crudo, sin envoltorio `data`):

```
{
  "access_token": "…",
  "id_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "openid kwid email profile company:read"
}
```

## 4 · Validar el token

Antes de confiar en el `id_token`, verificá su firma con el JWKS y chequeá los claims. Leé el `jwks_uri` desde discovery — no lo hardcodees.

| Chequeo | Qué validar |
| --- | --- |
| Firma | Verificá con las llaves de `jwks_uri` (de discovery). |
| iss | Debe ser `https://dev.kydhub.com`. |
| aud | Debe ser tu `client_id`. |
| exp | No expirado. |
| nonce | Igual al que mandaste (si lo usaste). |

## GETUserInfo

**Para qué sirve:** traer los datos del usuario logueado. Llamás con el `access_token` como Bearer.

```
GET /userinfo
Authorization: Bearer ACCESS_TOKEN

{
  "sub": "person_01JDEVEXAMPLE",
  "kwid": "BOB.CARTER",
  "email": "bob.carter@example.test",
  "email_verified": true,
  "name": "Bob Carter",
  "given_name": "Bob",
  "family_name": "Carter",
  "picture": null
}
```

## Claims por scope

Cada scope que pedís habilita ciertos claims:

| Scope | Claims que devuelve |
| --- | --- |
| openid | `sub, iss, aud, exp, iat, auth_time` (y `nonce` si lo mandaste). |
| kwid | `kwid`. |
| email | `email, email_verified`. |
| profile | `name, given_name, family_name, picture` (sujeto a la brecha de arriba). |

## Errores OAuth: intermedios vs terminales

Los intermedios se resuelven siguiendo el flujo; los terminales requieren reiniciar.

#### ↻ Intermedios

- `verification_required` — iniciá verificación por email.
- `pending_consent` / `consent_required` — esperá el consentimiento hosteado.
- `login_required` — iniciá login si no hay SSO.
#### Terminales

- `invalid_grant` — code expirado/usado, redirect o PKCE mismatch.
- `invalid_verification_identifier` — KWID inválido.
- `fresh_authentication_required` — reiniciá con `prompt=login`.
- `access_denied` — el usuario canceló.
## Referencia rápida Data API

Esta referencia reúne las capacidades canónicas de Data API. Usala como índice rápido; para detalles completos, leé cada sección específica.

**Regla contractual:** la Data API es credencializada y de sólo lectura. No crea, edita ni elimina personas, empresas, direcciones, API keys, OAuth clients ni webhooks.

**Autorización en tres niveles:** cada request necesita (1) una empresa activa con una `X-API-KEY` server-side válida, (2) los scopes requeridos, y (3) un grant activo, consentimiento, relación autorizada u otra base explícita de acceso para los datos devueltos. KydHub deriva la empresa solicitante desde la API key. **Sin directorio abierto:** query company es para empresas conocidas y autorizadas.

### Endpoints didácticos

| Endpoint | Capacidad | Identificadores | Fields requeridos |
| --- | --- | --- | --- |
| QUERY /api/v1/persons/query | Consultar datos de persona. | `person_kyd` o `person_id`. | Sí, lista explícita. |
| QUERY /api/v1/persons/query | Consultar direcciones de persona. | `person_kyd` o `person_id`. | Sí, campos `addresses.*`. |
| QUERY /api/v1/companies/query | Consultar identidad autorizada de empresa. | `company_kyd` conocido o identificador de empresa soportado. | `company.identity:read` más base válida de acceso. |
| QUERY /api/v1/companies/query | Consultar campos autorizados de perfil de empresa. | `company_kyd` conocido o identificador de empresa soportado. | `company.profile:read` más base válida de acceso. |
| QUERY /api/v1/companies/query | Consultar ubicaciones autorizadas de empresa. | `company_kyd` conocido o identificador de empresa soportado. | `company.locations:read` más base válida de acceso. |

### Request base

```
QUERY https://dev.api.kydhub.com/api/v1/persons/query
X-API-KEY: $KYDHUB_API_KEY
Content-Type: application/json

{
  "person_kyd": "BOB.CARTER",
  "service_name": "acme-checkout",
  "fields": ["full_name"]
}
```

### Respuesta base

```
{
  "data": {
    "person_id": "per_7Q9M2K4R",
    "person_kyd": "BOB.CARTER",
    "fields": {
      "full_name": "Bob Carter"
    }
  }
}
```

### Reglas rápidas

| Regla | Detalle |
| --- | --- |
| API key | Siempre en `X-API-KEY` y sólo desde backend. |
| fields | Siempre explícitos; no dependas de defaults amplios. |
| IDs públicos | Usá `per_...` y `comp_...`; nunca UUIDs internos. |
| Empresa | Usá `company_kyd` o `company_id`; no hay búsqueda abierta. |
| Errores | Si falta scope/grant/campo, la API falla cerrado y no expone datos. |

## POSTWebhooks

Los webhooks permiten que Acme reciba eventos firmados desde KydHub cuando algo relevante ocurre. Son opcionales: si tu integración sólo necesita consultar datos bajo demanda, podés usar la Data API sin webhooks.

### Flujo general

| Paso | Qué ocurre | Responsable |
| --- | --- | --- |
| 1 | Acme registra una URL receptora en el portal de KydHub. | Acme |
| 2 | KydHub genera o muestra el secreto de firma del webhook. | KydHub |
| 3 | Cuando ocurre un evento, KydHub envía un `POST` al backend de Acme. | KydHub |
| 4 | Acme valida la firma HMAC antes de procesar el evento. | Acme |
| 5 | Acme guarda el `event_id` para evitar procesar duplicados. | Acme |

### Evento recibido por Acme

```
POST https://app.acme.example/kydhub/webhook
X-KydHub-Event-Id: evt_01HVEXAMPLE
X-KydHub-Timestamp: 1760000000
X-KydHub-Signature-256: sha256=HEX_HMAC
Content-Type: application/json

{
  "id": "evt_01HVEXAMPLE",
  "type": "grant.approved",
  "created_at": "2026-06-28T12:00:00Z",
  "data": {
    "person_kyd": "BOB.CARTER",
    "company_id": "comp_42P7K9M2",
    "scopes": ["person.identity:read", "person.addresses:read"]
  }
}
```

### Headers de seguridad

| Header | Qué significa | Qué debe hacer Acme |
| --- | --- | --- |
| X-KydHub-Event-Id | ID único del evento. | Guardalo antes de procesar para que los reintentos sean idempotentes. |
| X-KydHub-Timestamp | Momento en que KydHub firmó/envió el evento. | Rechazá timestamps demasiado viejos para reducir replays. |
| X-KydHub-Signature-256 | Firma HMAC-SHA256 del body crudo. | Recalculá la firma con el secreto del webhook y compará timing-safe. |
| Content-Type | Formato del body. | Esperá `application/json`. |

### Verificar firma en Node.js

```
import crypto from 'node:crypto';

function verifyKydHubWebhook(rawBody, signatureHeader, webhookSecret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', webhookSecret)
    .update(rawBody)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}
```

### Eventos típicos

| Evento | Cuándo ocurre | Qué debería hacer Acme |
| --- | --- | --- |
| grant.approved | Una persona aprobó que Acme lea ciertos datos. | Actualizar estado interno y habilitar consulta Data API. |
| grant.rejected | Una persona rechazó la solicitud. | Mostrar alternativa o pedir menos datos. |
| grant.revoked | Una autorización fue revocada. | Dejar de usar datos que dependan de ese grant. |
| webhook.delivery.failed | KydHub no pudo entregar un evento tras intentos. | Revisar disponibilidad del endpoint receptor. |

### Reintentos, idempotencia y límites

| Tema | Regla | Recomendación |
| --- | --- | --- |
| Respuesta exitosa | Respondé `2xx` sólo después de validar firma y guardar el evento. | No hagas trabajo lento antes de responder; enviá a una cola si hace falta. |
| Reintentos | KydHub puede reintentar entregas fallidas. | Procesá por `event_id` para evitar duplicados. |
| Company Free | 1 webhook activo y 1000 intentos de entrega por mes. | Los reintentos cuentan contra el límite mensual. |
| Seguridad | El secreto de webhook vive sólo en backend/secret manager. | No lo pongas en frontend, logs ni documentación pública. |

## Seguridad, scopes, grants y límites

La seguridad de KydHub combina credenciales server-only, un catálogo completo de scopes solicitables, autorizaciones activas y límites de uso. La regla base es simple: la Data API es de sólo lectura y devuelve únicamente los scopes/campos específicos que fueron pedidos y autorizados.

### Reglas principales

#### Hacé

- Guardá API keys, OAuth secrets y webhook secrets sólo en backend o secret manager.
- Pedí sólo los campos necesarios en `fields`.
- Validá scopes y grants antes de depender de una respuesta.
- Verificá firmas de webhook con HMAC y comparación timing-safe.
- Rotá credenciales cuando una persona del equipo pierde acceso.
- Mostrá alternativas cuando falte autorización o un campo no esté disponible.
#### No hagas

- No pongas API keys en browser, apps móviles, repositorios ni documentación.
- No pongas `client_secret` dentro de una app distribuida.
- No uses KydHub como directorio abierto de empresas.
- No inventes datos si KydHub no devuelve un campo.
- No ignores `grant_required` o `field_not_allowed`.
- No intentes modificar personas, empresas, direcciones, API keys, OAuth clients o webhooks por la Data API pública.
### Modelo de autorización

| Capa | Qué valida | Ejemplo |
| --- | --- | --- |
| API key | Qué empresa está llamando y si la key está activa. | Acme llama con `X-API-KEY`. |
| Plan/límites | Cuántas credenciales, requests o entregas puede usar la empresa. | Company Free permite 2 API keys activas. |
| Scopes | Qué familias de datos puede leer una credencial. | `person.addresses:read`. |
| Grant | Qué persona/empresa autorizó la lectura y para qué campos. | Bob Carter autorizó lectura de identidad y direcciones. |
| Fields | Qué atributos exactos pide el request. | `["full_name", "addresses.city"]`. |

### Scopes actuales documentados

### Catálogo de scopes y aprobación del usuario

KydHub publica un catálogo completo y granular de scopes. Las empresas Pro y Enterprise pueden solicitar cualquier scope publicado; KydHub no bloquea una categoría de dato por plan. El usuario aprueba o rechaza exactamente los scopes de datos personales solicitados. Los planes limitan capacidad operativa: volumen de requests, rate limits, API keys, OAuth clients, webhooks, retención, soporte y SLA.

Ejemplos típicos:

| App | Scopes normalmente solicitados | Por qué |
|---|---|---|
| qbtChat | `person.identity:read`, `person.name:read`, `person.email:read`, `person.profile.avatar:read` | Login/perfil visible; no necesita dirección. |
| Mercado Libre | `person.identity:read`, `person.name:read`, `person.identity.document:read`, `person.addresses.primary:read` | Verificación de identidad y envío después de aprobación explícita del usuario. |


| Scope | Permite leer | Usado en |
| --- | --- | --- |
| person.identity:read | Identificadores y nombre básico de persona. | Consultar persona. |
| person.profile:read | Perfil básico autorizado de persona. | Consultar persona. |
| person.addresses:read | Direcciones registradas/autorizadas de persona. | Direcciones de persona. |
| company.identity:read | Identidad básica de empresa. | Consultar empresa. |
| company.profile:read | Perfil autorizado de empresa. | Consultar empresa. |
| company.locations:read | Ubicaciones/direcciones registradas de empresa. | Direcciones de empresa. |
| webhooks.events:read | Eventos webhook si se exponen diagnósticos. | Diagnóstico/operación. |
| usage:read | Uso y consumo de API si el contrato lo habilita. | Diagnóstico/operación. |
| audit:read | Eventos de auditoría permitidos. | Diagnóstico/operación. |

### Company Free limits

| Área | Límite | Notas |
| --- | --- | --- |
| Personas por empresa | 2 total | 1 owner/founder y 1 collaborator. |
| API keys | 2 activas | Pueden no expirar; expiración opcional. |
| API rate limit | 60 requests/min | Aplicado por empresa/API key según contrato operativo. |
| API daily limit | 2.000 requests/día | Diseñado para pilotos e integraciones iniciales. |
| Webhooks | 1 activo | 1000 intentos de entrega/mes; los retries cuentan. |
| OAuth / Apps | 1 connected app, 1 OAuth client | Hasta 3 redirect URIs. |
| Audit logs | 7 días | Retención básica. |
| Soporte | Basic / best-effort | Sin SLA contractual. |

### Operaciones permitidas y prohibidas

| Tipo | Estado | Explicación |
| --- | --- | --- |
| Leer datos autorizados de persona | Permitido | Con API key, scope de catálogo, grant/consentimiento/base válida activa y fields explícitos. |
| Leer direcciones autorizadas de persona | Permitido | Con permiso de direcciones. |
| Leer datos autorizados de empresa | Permitido | Con `company_kyd` o `company_id` conocido. |
| Leer ubicaciones autorizadas de empresa | Permitido | Con `company.locations:read`. |
| Crear/modificar personas por API pública | Prohibido | La persona modifica sus datos dentro de KydHub. |
| Crear/modificar empresas por API pública | Prohibido | Se administra desde el portal. |
| Administrar API keys/OAuth/webhooks por API pública | Prohibido | Por ahora se hace desde el portal. |
| Buscar empresas sin identificador | Prohibido | KydHub no debe funcionar como directorio abierto. |

### Respuesta segura ante falta de permiso

```
{
  "error": "field_not_allowed",
  "message": "One or more requested fields are not allowed for this API key or grant.",
  "fields": ["addresses.line1"]
}
```

## Errores y troubleshooting

Esta sección resume los errores más comunes al integrar KydHub. La recomendación general es no inventar datos ni continuar como si la respuesta hubiera sido exitosa: cada error indica una acción concreta para corregir credenciales, permisos, identificadores o configuración.

### Formato recomendado de error

```
{
  "error": "field_not_allowed",
  "message": "One or more requested fields are not allowed for this API key or grant.",
  "request_id": "req_01HVEXAMPLE",
  "fields": ["addresses.line1"]
}
```

| Atributo | Qué significa | Cómo usarlo |
| --- | --- | --- |
| error | Código estable del error. | Usalo para lógica de UI/backend. |
| message | Explicación legible. | Mostrala sólo si no expone datos internos; podés mapearla a copy propio. |
| request_id | ID de correlación. | Incluilo al pedir soporte o revisar logs. |
| fields | Campos afectados, cuando aplica. | Útil para remover campos o pedir autorización correcta. |

### API keys y autenticación

| Error | Qué significa | Cómo resolverlo |
| --- | --- | --- |
| missing_api_key | No llegó el header `X-API-KEY`. | Enviá la API key desde el backend de Acme. No la envíes desde browser. |
| invalid_api_key | La key no existe, fue revocada, expiró o está mal copiada. | Creá o rotá una key desde `Company → API Access`. |
| api_key_limit_reached | La empresa ya tiene el máximo de API keys activas. | En Company Free, revocá una key activa antes de crear otra. |
| rate_limit_exceeded | Se superó el límite de requests por minuto. | Reducí frecuencia, agregá backoff y revisá límites del plan. |
| daily_limit_exceeded | Se superó el límite diario. | Esperá reinicio de ventana o consultá upgrade/contrato. |

### Identificadores y recursos

| Error | Qué significa | Cómo resolverlo |
| --- | --- | --- |
| person_not_found | No existe persona para ese `person_kyd` o `person_id`. | Verificá el identificador. No intentes adivinar identidades. |
| company_not_found | No existe empresa para ese `company_kyd` o `company_id`. | Confirmá el identificador por canal autorizado; KydHub no es directorio abierto. |
| invalid_identifier | El identificador tiene formato inválido. | Usá `BOB.CARTER`, `per_...`, `company_kyd` o `comp_...` según corresponda. |
| address_not_available | No hay dirección de persona disponible/autorizada. | Pedí menos campos o mostrale al usuario una alternativa. |
| location_not_available | No hay ubicación empresarial disponible/autorizada. | Continuá sin ubicación o pedí un campo menos sensible. |

### Scopes, grants y fields

| Error | Qué significa | Cómo resolverlo |
| --- | --- | --- |
| grant_required | No hay autorización activa para leer esos datos. | Iniciá el flujo de autorización o pedí campos que no requieran ese grant. |
| grant_revoked | La autorización existía pero fue revocada. | Dejá de usar esos datos y solicitá nueva autorización si corresponde. |
| field_not_allowed | Uno o más campos no están permitidos por scope/grant. | Quitá campos, pedí menos datos o solicitá permisos adecuados. |
| scope_required | La API key no tiene el scope necesario. | Solicitá/habilitá el scope exacto. El usuario decide la autorización de datos personales; los planes limitan volumen/capacidad. |
| company_scope_required | Falta scope de lectura de empresa. | Usá scopes como `company.identity:read` o `company.profile:read`. |
| company_locations_scope_required | Falta permiso para ubicaciones de empresa. | Revisá si la key tiene `company.locations:read`. |

### OAuth / Login con KWID

| Error | Qué significa | Cómo resolverlo |
| --- | --- | --- |
| login_required | El usuario no tiene sesión activa. | Mostrá el flujo de verificación o redirigí a KydHub. |
| verification_required | Debe completar verificación por email/magic link. | No lo trates como fallo fatal; mostrale “Revisá tu email”. |
| invalid_redirect_uri | La URL callback no coincide exactamente con la registrada. | Registrá `https://app.acme.example/callback` o la URL real exacta. |
| invalid_client | `client_id` o secreto incorrecto. | Revisá el OAuth client en el portal. No pongas secrets en apps públicas. |
| invalid_grant | El `code` expiró, ya fue usado o no corresponde al verifier. | Reiniciá login y validá PKCE/state. |
| invalid_token | Token inválido, expirado o con firma incorrecta. | Validá issuer, audience, expiración y JWKS. |

### Webhooks

| Error | Qué significa | Cómo resolverlo |
| --- | --- | --- |
| webhook_signature_invalid | La firma HMAC no coincide. | Usá el raw body exacto y el secreto correcto; compará timing-safe. |
| webhook_timestamp_expired | El timestamp es demasiado viejo. | Rechazá el evento y revisá reloj/sincronización. |
| webhook_duplicate_event | El `event_id` ya fue procesado. | No lo reproceses; devolvé 2xx si ya quedó persistido. |
| webhook_delivery_failed | KydHub no pudo entregar el evento. | Revisá disponibilidad pública, TLS y respuesta 2xx del endpoint. |
| webhook_limit_reached | Se alcanzó el límite de webhooks activos o entregas. | En Company Free hay 1 webhook activo y 1000 intentos/mes. |

### Cifrado post-cuántico opcional

| Error | Qué significa | Cómo resolverlo |
| --- | --- | --- |
| pq_encryption_not_enabled | La capa no está habilitada para la empresa. | Usá Data API normal o pedí habilitación contractual. |
| unknown_key_id | El `kid` no existe o fue rotado. | Registrá/obtené la clave pública vigente. |
| decrypt_failed | No se pudo descifrar o falló AEAD. | No proceses contenido; reintentá con clave válida. |
| unsupported_algorithm | La suite criptográfica no está soportada. | Negociá una suite soportada o desactivá esta capa. |

## Agent-readable / matriz de endpoints

Esta sección resume la integración de KydHub en formato compacto para agentes, equipos técnicos y documentación interna. No reemplaza las secciones anteriores: sirve como mapa rápido para implementar sin perder las reglas de seguridad.

### Resumen operativo

| Elemento | Valor | Nota |
| --- | --- | --- |
| Ambiente sandbox/dev | `https://dev.api.kydhub.com/api/v1` | Usado para pruebas y documentación actual. |
| Auth Data API | `X-API-KEY` | Sólo backend/secret manager. Nunca browser. |
| Empresa ejemplo | `Acme` | Empresa integradora ficticia. |
| Persona ejemplo | `Bob Carter / BOB.CARTER` | Persona ficticia para ejemplos. |
| Empresa consultada ejemplo | `ACME.SUPPLIER` | KYD empresarial ficticio. |
| Modelo de API | Sólo lectura | No crea, modifica ni elimina datos por API pública. |

### Matriz de capacidades actuales

| Capacidad | Endpoint didáctico | Identificadores | Scopes típicos |
| --- | --- | --- | --- |
| Consultar persona | `QUERY /api/v1/persons/query` | `person_kyd` o `person_id` | `person.identity:read`, `person.profile:read` |
| Consultar direcciones de persona | `QUERY /api/v1/persons/query` | `person_kyd` o `person_id` | `person.addresses:read` |
| Consultar identidad de empresa | `QUERY /api/v1/companies/query` | `company_kyd` conocido o identificador de empresa soportado | `company.identity:read` |
| Consultar perfil de empresa | `QUERY /api/v1/companies/query` | `company_kyd` conocido o identificador de empresa soportado | `company.profile:read` |
| Consultar ubicaciones de empresa | `QUERY /api/v1/companies/query` | `company_kyd` conocido o identificador de empresa soportado | `company.locations:read` |
| Login con KWID | `GET /oauth/authorize` | `client_id`, `redirect_uri` | `openid`, `kwid`, `email`, `profile` |
| Webhooks | `POST https://app.acme.example/kydhub/webhook` | `event_id` | Firma HMAC + eventos contratados. |

### JSON para agentes

```
{
  "product": "KydHub",
  "environment": "sandbox",
  "base_url": "https://dev.api.kydhub.com/api/v1",
  "auth": {
    "data_api": {
      "type": "api_key",
      "header": "X-API-KEY",
      "storage": "backend_or_secret_manager_only"
    },
    "oauth_oidc": {
      "optional": true,
      "issuer": "https://dev.kydhub.com",
      "flow": "authorization_code_pkce"
    }
  },
  "read_only_public_api": true,
  "examples": {
    "integrator_company": "Acme",
    "person_kyd": "BOB.CARTER",
    "company_kyd": "ACME.SUPPLIER",
    "person_id": "per_7Q9M2K4R",
    "company_id": "comp_42P7K9M2"
  }
}
```

### Operaciones permitidas

| Operación | Permitida | Condición |
| --- | --- | --- |
| Leer campos autorizados de persona | Sí | API key válida + scope de catálogo + grant/consentimiento/base válida + fields explícitos. |
| Leer direcciones autorizadas de persona | Sí | `person.addresses:read` y autorización activa. |
| Leer campos autorizados de empresa | Sí | `company_kyd` o `company_id` conocido. |
| Leer ubicaciones autorizadas de empresa | Sí | `company.locations:read`. |
| Usar Login con KWID/OIDC | Opcional | Sólo si Acme decide delegar login/verificación. |
| Recibir webhooks firmados | Opcional | Webhook registrado en portal + secreto HMAC. |

### Operaciones prohibidas para agentes

| Operación | Estado | Regla |
| --- | --- | --- |
| Modificar datos de personas por API pública | No | La persona administra sus datos dentro de KydHub. |
| Modificar empresas por API pública | No | La empresa se administra desde el portal. |
| Crear API keys por API pública | No | Las API keys se crean desde el portal. |
| Crear OAuth clients por API pública | No | Los OAuth clients se administran desde el portal. |
| Buscar empresas sin identificador | No | KydHub no es directorio abierto. |
| Exponer secrets en código o logs | Nunca | Usar variables de entorno o secret manager. |

### Checklist para implementar

| # | Validación | Resultado esperado |
| --- | --- | --- |
| 1 | Crear API key en portal. | Secret guardado en backend/secret manager. |
| 2 | Hacer request mínimo de persona. | Respuesta contiene sólo campos pedidos/autorizados. |
| 3 | Probar falta de scope/campo no permitido. | Error seguro tipo `field_not_allowed`. |
| 4 | Si hay OAuth, validar `state`, `nonce` y JWKS. | Login seguro y tokens verificados. |
| 5 | Si hay webhooks, validar HMAC e idempotencia. | Eventos firmados y sin procesamiento duplicado. |
| 6 | Documentar errores esperados en la integración. | UX con alternativas y sin inventar datos. |

## Cifrado post-cuántico opcional

KydHub puede ofrecer una capa opcional de cifrado híbrido post-cuántico para payloads especialmente sensibles. Esta capa protege el cuerpo del mensaje además de HTTPS/TLS, pero no reemplaza autenticación, autorización, scopes, grants, auditoría ni firmas.

### Cuándo usarlo

| Situación | Recomendación | Motivo |
| --- | --- | --- |
| Integración estándar de Data API | No es obligatorio. | TLS, API key, scopes y grants ya son la base de seguridad. |
| Payloads con PII sensible | Puede activarse si el contrato lo habilita. | Agrega confidencialidad del body además del canal TLS. |
| Webhooks con datos sensibles | Puede usarse junto con firma HMAC. | La firma verifica integridad/origen; el cifrado protege contenido. |
| Cliente sin soporte criptográfico | No lo actives todavía. | Primero integrá Data API normal y luego agregá esta capa. |

### Qué no reemplaza

| Control | Sigue siendo obligatorio | Por qué |
| --- | --- | --- |
| TLS/HTTPS | Sí | Protege el canal de transporte. |
| API key | Sí | Identifica a la empresa que llama. |
| OAuth/OIDC | Sí, si usás Login con KWID | Autentica usuarios y emite claims/tokens. |
| Scopes y grants | Sí | Deciden qué datos pueden leerse. |
| Firma de webhooks | Sí | Verifica origen e integridad del evento. |
| Auditoría | Sí | Permite rastrear quién pidió qué y cuándo. |

### Cómo funciona a alto nivel

| Paso | Qué pasa | Resultado |
| --- | --- | --- |
| 1 · Clave pública | El receptor publica o registra una clave pública de cifrado. | El emisor sabe con qué clave proteger el payload. |
| 2 · KEM post-cuántico | El emisor encapsula un secreto usando ML-KEM/Kyber. | Se obtiene un secreto compartido resistente al modelo post-cuántico. |
| 3 · ECDH clásico | Se genera además un secreto X25519 efímero. | Defensa híbrida clásica + post-cuántica. |
| 4 · Derivación | Ambos secretos entran a HKDF. | Se deriva una clave simétrica para cifrar. |
| 5 · AEAD | El payload se cifra con AES-256-GCM u otro AEAD aprobado. | Confidencialidad e integridad del cuerpo cifrado. |

### Ejemplo de envoltorio cifrado

```
{
  "protected_payload": {
    "alg": "KydHub-HybridPQ-MLKEM768-X25519-HKDF-AES256GCM",
    "kid": "kh_pq_key_2026_06",
    "kem_ciphertext": "BASE64URL_ML_KEM_CT",
    "ecdh_public_key": "BASE64URL_X25519_PUB",
    "nonce": "BASE64URL_96BIT",
    "ciphertext": "BASE64URL_AEAD_CT",
    "aad": "method=QUERY;path=/api/v1/persons/query;event=grant.approved"
  }
}
```

### Atributos del envoltorio

| Atributo | Qué significa | Regla |
| --- | --- | --- |
| alg | Algoritmo/paquete criptográfico usado. | Debe estar acordado por KydHub y Acme. |
| kid | ID de la clave pública usada. | Permite rotar claves sin romper integraciones. |
| kem_ciphertext | Ciphertext del KEM post-cuántico. | Lo usa el receptor para recuperar el secreto compartido. |
| ecdh_public_key | Clave pública efímera X25519 del emisor. | Participa en la derivación híbrida. |
| nonce | Nonce usado por el cifrado AEAD. | No debe repetirse para la misma clave. |
| ciphertext | Payload cifrado. | Contiene los datos reales protegidos. |
| aad | Datos autenticados no cifrados. | Vincula el payload con método, path, evento o contexto. |

### Errores y fallback

| Error | Qué significa | Qué hacer |
| --- | --- | --- |
| pq_encryption_not_enabled | La empresa no tiene habilitada esta capa. | Usá Data API normal o solicitá habilitación. |
| unknown_key_id | El `kid` no existe o fue rotado. | Obtené/registrá la clave pública actual. |
| decrypt_failed | El payload no pudo descifrarse o la autenticación AEAD falló. | No proceses el contenido; registrá el error y reintentá con una clave válida. |
| unsupported_algorithm | El algoritmo no está soportado por una de las partes. | Negociá una suite soportada o desactivá esta capa. |


## Regla de verbos para Data API query

Usá `GET` para lecturas simples de recurso con un identificador en path. Usá `QUERY /query` para consultas read-only seguras e idempotentes que necesitan contenido JSON estructurado. Endpoints canónicos de query:

- `QUERY /api/v1/persons/query`
- `QUERY /api/v1/companies/query`

Las lecturas simples de empresa usan GET con identificador directo:

- `GET /api/v1/companies/{company_identifier}`
- `GET /api/v1/companies/{company_identifier}/profile`
- `GET /api/v1/companies/{company_identifier}/locations`

El response de Query company usa `company_id`, `company_kyd`, `service_name`, `response_profile_used` y `fields.identity/profile/locations`; no va envuelto en `data` para el response canónico de Data API query.
