Autenticación, límites y errores
Clave de API
Section titled “Clave de API”Todos los endpoints piden una clave de API en la cabecera Authorization, con el esquema Bearer:
Authorization: Bearer sk-normatia-...Las claves tienen la forma sk-normatia- seguida de 64 caracteres hexadecimales, y las de prueba, sk-normatia-test- seguida de otros 64. Se crean en normatia.com, en Mi cuenta, apartado Claves API, con el botón Nueva clave o, las de prueba, Clave de prueba. Normatia solo guarda una huella de la clave, así que la clave completa se ve una sola vez, al crearla: guárdala en un gestor de secretos y no la subas a ningún repositorio.
Acceso por organización
Section titled “Acceso por organización”La API y el servidor MCP se activan por organización, cada uno por su lado (api_enabled y mcp_enabled en GET /v1/me): una organización puede tener los dos o solo uno, sea cual sea su plan. Para activarlos, escribe a hola@normatia.com. Una clave de prueba no necesita ninguno: la crea cualquier miembro y responde con el entorno de pruebas, solo en la API REST.
Cada clave pertenece a una de tus organizaciones, la que tiene activada la API o el MCP al crearla. En la API REST, una clave de una organización sin la API recibe 403 api_not_enabled; en el servidor MCP, sin el MCP, 403 mcp_not_enabled. Si dejas la organización, la clave deja de funcionar. Un cambio en la activación tarda hasta 5 minutos en notarse.
Permisos
Section titled “Permisos”Cada clave tiene uno o varios de estos permisos, que se eligen al crearla:
| Permiso | Qué deja hacer | Gasta |
|---|---|---|
data | Las rutas de datos: parcelas, capas, cobertura, municipios, normativa, localización y proyectos. En el MCP, las herramientas de datos | No |
ask | Las consultas y comprobaciones (POST /api/v2/ask, POST /api/v1/verify). En el MCP, ask y get_answer | Sí, el cupo de la organización |
mcp | Entrar en el servidor MCP | No |
GET /v1/me, GET /v1/usage y las rutas de /v1/keys responden con cualquier permiso. Una clave sin el permiso que pide la ruta recibe 403 insufficient_scope, con el permiso que falta en required_scope y en la cabecera WWW-Authenticate. En el MCP, el asistente solo ve las herramientas que la clave puede usar.
Así, por ejemplo, a una aplicación que solo lee datos le basta una clave con data: lee todo, pero no puede gastar las consultas de tu organización. Una clave con permisos limitados solo puede revocarse a sí misma. Las claves de prueba tienen solo data, y las creadas antes de noviembre de 2026, los tres. GET /v1/me y GET /v1/keys dicen los permisos de cada una en scopes.
La clave actúa en tu nombre
Section titled “La clave actúa en tu nombre”Una clave actúa como el usuario que la creó, pero solo dentro de su organización: ve los proyectos de esa organización y no los de otras a las que pertenezca el usuario. Un proyecto de otra organización responde como si no existiera (404 project_not_found), y si el proyecto activo en la web es de otra, las rutas que lo usan piden project_id (400 no_active_project). Las consultas y comprobaciones cuentan en el consumo de la organización de la clave y se rigen por su plan. No hay filtro por país: hoy el catálogo es de España.
La clave es personal: no la compartas con nadie de fuera de tu organización. Quien necesite usar la API, aunque sea para probarla, tiene que tener su propia cuenta. Si es de tu organización, invítalo a ella; si es de otra empresa, abre su cuenta y su organización y le activamos la API. Lo dicen las condiciones de uso de la API.
Límites por minuto
Section titled “Límites por minuto”Las rutas se agrupan por lo que hacen. Cada clave tiene un contador por grupo, en ventanas de 60 segundos que empiezan con la primera petición.
| Grupo | Rutas | Límite | Consume cuota |
|---|---|---|---|
| Datos | Todas las de /v1, y /api/v1/location/..., /api/v1/codes/..., /api/v1/projects y /api/v1/project/info | 60 peticiones por minuto | No |
| Inteligencia | POST /api/v2/ask, POST /api/v1/verify | El límite por minuto de tu plan (intelligence_per_minute en GET /v1/me) | Sí, 1 crédito por llamada |
| MCP | Todas las peticiones al servidor MCP | 60 peticiones por minuto | Solo la herramienta ask, que cuenta además en el grupo de inteligencia |
| Claves de prueba | Las rutas del entorno de pruebas | 30 peticiones por minuto | No |
Cada respuesta lleva el estado del límite de su grupo en las cabeceras RateLimit-Limit, RateLimit-Remaining y RateLimit-Reset (segundos hasta la ventana siguiente). Al superarlo, la API responde 429 rate_limit_exceeded con la cabecera Retry-After y, en el cuerpo, el grupo, el límite y la cuenta actual. En /v1, en el formato RFC 9457 de Errores:
{ "type": "https://docs.normatia.com/api-reference/errores/#rate_limit_exceeded", "title": "Demasiadas peticiones", "status": 429, "detail": "Has superado las 60 peticiones por minuto. Vuelve a intentarlo en 23 s.", "code": "rate_limit_exceeded", "group": "data", "limit_rpm": 60, "current": 61, "request_id": "req_4f0c2a9e6b1d4c3e8a7f5b2d1e0c9a8b"}En /api/v1 y /api/v2, dentro de detail:
{ "detail": { "error": "rate_limit_exceeded", "message": "Has superado las 60 peticiones por minuto. Vuelve a intentarlo en 23 s.", "group": "intelligence", "limit_rpm": 60, "current": 61 }}La cuota es la de la organización de la clave, la misma que gastáis al usar la web. Solo la consumen los endpoints del grupo de inteligencia:
POST /api/v2/askconsume 1 crédito por consulta, encadene el agente una búsqueda o seis. Si el proyecto no tiene ninguna normativa ni documento seleccionado, la respuesta lo explica y no consume crédito.POST /api/v1/verifyconsume 1 crédito por verificación. Si el modelo de lenguaje falla o no da una respuesta válida, responde502 llm_unavailabley no consume.
En los planes con cuota mensual, los créditos se renuevan el día 1 de cada mes. Cuando se agotan, los endpoints de inteligencia responden 429 quota_exceeded hasta la renovación; los de datos siguen funcionando.
{ "detail": { "error": "quota_exceeded", "message": "Tu organización ha agotado las consultas de este mes.", "plan_name": "pro", "remaining": 0, "reset_at": "2026-12-01T00:00:00+00:00" }}reset_at es null en los planes sin renovación mensual. Con renovación, la respuesta lleva Retry-After.
El consumo de cada clave y de su organización se consulta en GET /v1/usage.
Formato de los errores
Section titled “Formato de los errores”Esta sección describe las rutas de /api/v1 y /api/v2. Las de /v1 usan el formato estándar RFC 9457, descrito en Errores. En las dos, cada respuesta lleva la cabecera X-Request-Id: indícala si nos escribes por un problema.
Los errores de la API llegan con el código HTTP correspondiente y un cuerpo JSON con un único campo, detail, que contiene un objeto con el código de error en error y, casi siempre, una explicación en message:
{ "detail": { "error": "project_not_found", "message": "El proyecto indicado no existe o no tienes acceso a él. Usa la lista de proyectos para ver los disponibles." }}Tenlo en cuenta al leerlos:
- El código está en
detail.errory el texto endetail.message, no en la raíz del cuerpo. - Los
429llevan además otros campos (ver arriba). El401 invalid_api_keyde una clave que no existe no traemessage. - Los mensajes pueden estar en inglés o en español. Para decidir qué hacer, usa siempre
detail.error, nunca el texto.
Hay dos casos con otra forma:
| Caso | Cuerpo |
|---|---|
Petición mal formada (422): falta un campo, sobra uno donde no se admiten o un valor no es válido | detail es una lista de errores de validación: {"detail": [{"type": "missing", "loc": ["body", "query"], "msg": "Field required", ...}]} |
Ruta que no existe (404) | {"detail": "Not Found"} |
Un cuerpo de más de 1 MB responde 413 payload_too_large, y un fallo interno responde 500 con {"detail": {"error": "internal_error", "message": "...", "request_id": "req_..."}}.
Códigos de error
Section titled “Códigos de error”| Estado | detail.error | Cuándo |
|---|---|---|
| 401 | missing_api_key | La petición no lleva Authorization: Bearer |
| 401 | invalid_api_key | La clave no empieza por sk-normatia-, no existe o está revocada |
| 403 | api_not_enabled | La organización de la clave no tiene activada la API, o el usuario ya no es miembro de ella |
| 403 | mcp_not_enabled | Lo mismo en el servidor MCP: la organización no tiene activado el MCP |
| 403 | insufficient_scope | La clave no tiene el permiso que pide la ruta (data, ask o mcp). Trae required_scope |
| 403 | not_available_in_sandbox | Una clave de prueba en una ruta que no está en el entorno de pruebas (todas las de /api) |
| 403 | test_key_not_supported | Una clave de prueba en el servidor MCP |
| 400 | no_active_project | No se indicó proyecto y el usuario no tiene proyecto activo (ask, project/info, verify) |
| 400 | no_project_location | El proyecto activo no tiene municipio (verify) |
| 404 | not_found | El geo_id, la normativa o la versión no existen (location, codes) |
| 404 | location_not_found | El geo_id no existe (verify) |
| 404 | project_not_found | El proyecto no existe o el usuario no tiene acceso |
| 422 | geo_id_mismatch | El geo_id no es el del proyecto activo (verify) |
| 422 | no_applicable_codes | Ninguna normativa aplica a ese municipio, o ninguna de las indicadas (verify) |
| 429 | rate_limit_exceeded | Se superó el límite por minuto del grupo |
| 429 | quota_exceeded | Se agotaron los créditos |
| 502 | llm_unavailable | El modelo de lenguaje no respondió o no dio una respuesta válida (verify). No consume crédito |
| 500 | internal_error | Un fallo nuestro. Escríbenos con el request_id |
La lista completa, con los códigos de /v1, está en Errores.