API de PassFortress
Una API REST 100% programática para gestionar tu bóveda de secretos cifrada del lado servidor. Crea una API key, llama a los endpoints y deja que tus aplicaciones lean y escriban secretos de forma segura.
Todos los endpoints viven bajo /api/v1/, relativos al host del despliegue. En estos ejemplos usamos https://api.passfortress.com como host de referencia. La API expone tus secretos, contenedores, identificadores, sitios web, IPs y perfil. Los metadatos (nombres, URLs, tipos) se sirven sin la master key; los valores descifrados exigen enviar la master key en cada petición (ver Master key y cifrado).
URL base
https://api.passfortress.com/api/v1
Autenticación
API key (Bearer)
Formato
JSON · UTF-8
- 1
Crea una API key en /developers/api-keys con los scopes que necesites. El token se muestra una sola vez: cópialo y guárdalo en un gestor de secretos o variable de entorno.
- 2
Haz tu primera petición: lista los metadatos de tus secretos (no requiere master key).
curl "https://api.passfortress.com/api/v1/secrets/" \
-H "Authorization: Bearer pf_live_xxx"La misma petición en varios lenguajes:
curl -X GET "https://api.passfortress.com/api/v1/secrets/?type=password&page=1" \
-H "Authorization: Bearer pf_live_xxx"¿Y los valores?
X-Master-Key con la master key del usuario. Por ejemplo, GET /secrets/{uuid}/value/ requiere el scope values:read y la master key.Los clientes programáticos se autentican con una API key en la cabeceraAuthorization. Las claves se crean en el panel web y se muestran una única vez.
Authorization: Bearer pf_live_xxxEntornos
El prefijo identifica el entorno: pf_live_ para producción y pf_test_ para pruebas. Mantén separadas las claves de cada entorno.
JWT (apps de usuario)
Las apps de usuario final pueden usar el flujo JWT (/auth/login/). Para integraciones servidor-a-servidor, usa API keys.
Nunca expongas una API key en el navegador
PassFortress cifra los valores de los secretos en el servidor con la master key del usuario, que nunca se almacena (ni en el servidor ni en los backups). Para leer o escribir un valor descifrado debes enviar la cabecera X-Master-Key: <master-key> en esa petición, además de la API key. Los metadatos (listar secretos, nombres, URLs, contenedores) no la requieren.
curl "https://api.passfortress.com/api/v1/secrets/SECRET_UUID/value/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "X-Master-Key: <tu-master-key>"428: falta la master key
X-Master-Key responde 428 Precondition Required con código master_key_required.403: master key incorrecta
master_key_invalid. No revela ningún secreto.¿Por qué 428 y no 401?
401 se confunde con un token caducado y dispararía un refresh/logout en muchos clientes, expulsando al usuario de una sesión válida. El 428 deja claro que la credencial es correcta pero falta una precondición: la master key.Implicación de confianza
| Scope | Recurso | Concede |
|---|---|---|
secrets:read | Secretos | Listar y leer los metadatos de los secretos (nombre, tipo, URL, contenedor). No descifra valores. |
secrets:write | Secretos | Crear, editar, duplicar y eliminar secretos. |
values:read | Secretos | Leer valores descifrados: valor actual, historial y descarga de archivos. Requiere también la master key. |
containers:read | Contenedores | Listar y leer contenedores (carpetas). |
containers:write | Contenedores | Crear, renombrar y eliminar contenedores. |
identifiers:read | Identificadores | Leer los pares clave-valor asociados a cada secreto. |
identifiers:write | Identificadores | Crear, editar y eliminar identificadores. |
websites:read | Sitios web | Listar los sitios web asociados a los secretos. |
websites:write | Sitios web | Crear y modificar sitios web. |
ips:read | IPs | Leer las listas blanca/negra de direcciones IP. |
ips:write | IPs | Añadir y eliminar reglas de IP. |
profile:read | Perfil | Leer los datos del perfil y las preferencias. |
profile:write | Perfil | Actualizar perfil, contraseña, tema y rotar la master key. |
share:write | Compartir | Compartir secretos y aceptar secretos compartidos. Requiere la master key. |
import:write | Importación | Importar secretos desde un CSV de Chrome. Requiere la master key. |
apikeys:read | API keys | Listar las API keys y sus metadatos. |
apikeys:writesensible | API keys | Crear, rotar y revocar API keys. Una clave con este scope puede acuñar otras claves. |
apikeys:write es peligroso
apikeys:write puede acuñar otras claves (con cualquier scope). Concédelo solo a integraciones de aprovisionamiento de confianza.Rate limits
El límite es por clave. Superarlo devuelve 429 con la cabecera Retry-After.
| Tier | Límite | Uso |
|---|---|---|
| default | 1 000 / hora | Límite por defecto para nuevas claves. |
| high | 10 000 / hora | Para integraciones con mayor volumen. |
| unlimited | Sin límite | Reservado para casos especiales. No disponible para claves con el scope values:read (devuelve invalid_rate_tier). |
Paginación
Las colecciones usan paginación por número de página, 50 elementos por página. Usa ?page=N y sigue el campo next hasta que sea null.
{
"count": 673,
"next": "https://api.passfortress.com/api/v1/secrets/?page=2",
"previous": null,
"results": [ /* … 50 elementos … */ ]
}Autenticación
Login con usuario/contraseña y gestión de la sesión JWT. Los clientes programáticos normalmente usan API keys; estos endpoints existen para apps de usuario final.
/api/v1/auth/login//api/v1/auth/register//api/v1/auth/token/refresh//api/v1/auth/me//api/v1/auth/logout//api/v1/auth/master-key/validate//api/v1/auth/verify/request//api/v1/auth/verify/confirm//api/v1/auth/password-reset//api/v1/auth/password-reset/confirm//api/v1/auth/login/Iniciar sesión y obtener tokens JWT.
Devuelve un access token (corta duración), un refresh token (1 año) y el usuario.
Cuerpo (JSON)
| username* | string | Nombre de usuario de la cuenta. |
| password* | string | Contraseña. |
Petición
curl -X POST "https://api.passfortress.com/api/v1/auth/login/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"username": "ada",
"password": "mi-contraseña"
}'Respuesta
{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"uuid": "c0ffee00-1111-2222-3333-444455556666",
"username": "ada",
"email": "dev@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"mobile": "+34600000000",
"address": "Madrid",
"language": "es",
"theme": "system",
"email_verified": true,
"mobile_verified": false,
"tfa_email_enabled": false,
"tfa_mobile_enabled": false,
"enabled_envfiles": true,
"enabled_files": true
}
}/api/v1/auth/register/Registrar un nuevo usuario.
Devuelve los tokens JWT y el usuario creado (201).
Cuerpo (JSON)
| username* | string | Nombre de usuario único. |
| email* | string | Correo único. |
| password* | string | Contraseña (mínimo 8). |
| master_key* | string | Master key inicial (mínimo 8; no repetitiva, secuencial ni común). |
| first_name | string | Nombre. |
| last_name | string | Apellidos. |
Petición
curl -X POST "https://api.passfortress.com/api/v1/auth/register/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"username": "ada",
"email": "nuevo@example.com",
"password": "mi-contraseña",
"master_key": "<tu-master-key>",
"first_name": "Ada"
}'Respuesta
{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"uuid": "c0ffee00-1111-2222-3333-444455556666",
"username": "ada",
"email": "dev@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"mobile": "+34600000000",
"address": "Madrid",
"language": "es",
"theme": "system",
"email_verified": true,
"mobile_verified": false,
"tfa_email_enabled": false,
"tfa_mobile_enabled": false,
"enabled_envfiles": true,
"enabled_files": true
}
}/api/v1/auth/token/refresh/Renovar el access token.
Cuerpo (JSON)
| refresh* | string | Refresh token vigente. |
Petición
curl -X POST "https://api.passfortress.com/api/v1/auth/token/refresh/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}'Respuesta
{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}/api/v1/auth/me/Obtener el usuario autenticado.
Devuelve el objeto del usuario directamente (sin envoltorio).
Petición
curl -X GET "https://api.passfortress.com/api/v1/auth/me/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"uuid": "c0ffee00-1111-2222-3333-444455556666",
"username": "ada",
"email": "dev@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"mobile": "+34600000000",
"address": "Madrid",
"language": "es",
"theme": "system",
"email_verified": true,
"mobile_verified": false,
"tfa_email_enabled": false,
"tfa_mobile_enabled": false,
"enabled_envfiles": true,
"enabled_files": true
}/api/v1/auth/logout/Cerrar sesión (revoca el refresh token).
Devuelve 205 Reset Content sin cuerpo. Idempotente.
Cuerpo (JSON)
| refresh* | string | Refresh token a invalidar. |
Petición
curl -X POST "https://api.passfortress.com/api/v1/auth/logout/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}'Respuesta
"205 Reset Content (sin cuerpo)"/api/v1/auth/master-key/validate/Validar la master key sin revelar secretos.
Para claves API requiere el scope values:read (validar la master key es la misma capacidad que leer un valor). Necesita también la cabecera X-Master-Key. Sujeto al lockout anti-fuerza-bruta: tras varios intentos fallidos devuelve 429 master_key_locked con Retry-After.
Cuerpo (JSON)
| master_key* | string | Master key a comprobar. |
Petición
curl -X POST "https://api.passfortress.com/api/v1/auth/master-key/validate/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"master_key": "<tu-master-key>"
}'Respuesta
{
"valid": true
}/api/v1/auth/verify/request/Solicitar un código de verificación.
Cuerpo (JSON)
| scope* | string | verify_email, verify_mobile, verify_email_tfa o verify_mobile_tfa. |
Petición
curl -X POST "https://api.passfortress.com/api/v1/auth/verify/request/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"scope": "verify_email"
}'Respuesta
{
"scope": "verify_email",
"channel": "email",
"sent": true
}/api/v1/auth/verify/confirm/Confirmar el código de verificación.
Cuerpo (JSON)
| scope* | string | El mismo scope usado en la solicitud. |
| code* | string | Código recibido. |
Petición
curl -X POST "https://api.passfortress.com/api/v1/auth/verify/confirm/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"scope": "verify_email",
"code": "123456"
}'Respuesta
{
"verified": true
}/api/v1/auth/password-reset/Solicitar el restablecimiento de contraseña.
Responde 200 siempre (no revela si el correo existe).
Cuerpo (JSON)
| email* | string | Correo de la cuenta. |
Petición
curl -X POST "https://api.passfortress.com/api/v1/auth/password-reset/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"email": "dev@example.com"
}'Respuesta
{
"sent": true
}/api/v1/auth/password-reset/confirm/Confirmar el restablecimiento con el código.
Cuerpo (JSON)
| email* | string | Correo de la cuenta. |
| code* | string | Código recibido por correo. |
| new_password* | string | Nueva contraseña (mínimo 8). |
Petición
curl -X POST "https://api.passfortress.com/api/v1/auth/password-reset/confirm/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"email": "dev@example.com",
"code": "123456",
"new_password": "nueva-contraseña"
}'Respuesta
{
"reset": true
}Perfil
Datos del usuario, contraseña, tema y rotación de la master key.
/api/v1/profile//api/v1/profile//api/v1/profile/password//api/v1/profile/master-key//api/v1/profile/theme//api/v1/profile/Leer el perfil del usuario.
Petición
curl -X GET "https://api.passfortress.com/api/v1/profile/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"uuid": "c0ffee00-1111-2222-3333-444455556666",
"username": "ada",
"email": "dev@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"mobile": "+34600000000",
"address": "Madrid",
"language": "es",
"theme": "system",
"email_verified": true,
"mobile_verified": false,
"tfa_email_enabled": false,
"tfa_mobile_enabled": false,
"enabled_envfiles": true,
"enabled_files": true
}/api/v1/profile/Actualizar campos del perfil.
Cuerpo (JSON)
| first_name | string | Nombre. |
| last_name | string | Apellidos. |
| mobile | string | Teléfono móvil. |
| address | string | Dirección. |
| language | string | Idioma preferido. |
| theme | string | light, dark o system. |
Petición
curl -X PATCH "https://api.passfortress.com/api/v1/profile/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Ada",
"last_name": "Lovelace"
}'Respuesta
{
"uuid": "c0ffee00-1111-2222-3333-444455556666",
"username": "ada",
"email": "dev@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"mobile": "+34600000000",
"address": "Madrid",
"language": "es",
"theme": "system",
"email_verified": true,
"mobile_verified": false,
"tfa_email_enabled": false,
"tfa_mobile_enabled": false,
"enabled_envfiles": true,
"enabled_files": true
}/api/v1/profile/password/Cambiar la contraseña de la cuenta.
Cuerpo (JSON)
| old_password* | string | Contraseña actual. |
| new_password* | string | Nueva contraseña (mínimo 8). |
Petición
curl -X POST "https://api.passfortress.com/api/v1/profile/password/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"old_password": "actual",
"new_password": "nueva"
}'Respuesta
{
"changed": true
}/api/v1/profile/master-key/Rotar la master key (re-cifra todos los secretos).
Descifra cada secreto con la master key antigua y lo vuelve a cifrar con la nueva. Ambas claves viajan en el cuerpo (no usa la cabecera X-Master-Key). Operación pesada e irreversible si se pierde la nueva clave.
Cuerpo (JSON)
| old_master_key* | string | Master key actual. |
| new_master_key* | string | Nueva master key (mínimo 8; no repetitiva, secuencial ni común). |
Petición
curl -X POST "https://api.passfortress.com/api/v1/profile/master-key/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"old_master_key": "<actual>",
"new_master_key": "<nueva>"
}'Respuesta
{
"changed": true
}/api/v1/profile/theme/Cambiar el tema preferido.
Devuelve el perfil completo actualizado.
Cuerpo (JSON)
| theme* | string | light, dark o system. |
Petición
curl -X PATCH "https://api.passfortress.com/api/v1/profile/theme/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"theme": "dark"
}'Respuesta
{
"uuid": "c0ffee00-1111-2222-3333-444455556666",
"username": "ada",
"email": "dev@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"mobile": "+34600000000",
"address": "Madrid",
"language": "es",
"theme": "dark",
"email_verified": true,
"mobile_verified": false,
"tfa_email_enabled": false,
"tfa_mobile_enabled": false,
"enabled_envfiles": true,
"enabled_files": true
}Países
Catálogo de países (datos de referencia, no requiere scope).
/api/v1/countries/Listar países disponibles.
Devuelve un array plano (sin paginación).
Petición
curl -X GET "https://api.passfortress.com/api/v1/countries/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
[
{
"id": 1,
"name_en": "Spain",
"name_es": "España",
"iso2": "ES",
"iso3": "ESP",
"phone_code": "34"
},
{
"id": 2,
"name_en": "Mexico",
"name_es": "México",
"iso2": "MX",
"iso3": "MEX",
"phone_code": "52"
}
]IPs
Listas blanca/negra de direcciones IP para el control de acceso de la cuenta.
/api/v1/ips/Listar reglas de IP.
Petición
curl -X GET "https://api.passfortress.com/api/v1/ips/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"uuid": "ip1",
"name": "Oficina",
"ip_range": "203.0.113.0/24",
"list": "white"
}
]
}/api/v1/ips/Añadir una regla de IP.
Cuerpo (JSON)
| ip_range* | string | IP o rango (CIDR). |
| list | string | white o black. |
| name | string | Etiqueta descriptiva (opcional). |
Petición
curl -X POST "https://api.passfortress.com/api/v1/ips/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Oficina",
"ip_range": "203.0.113.0/24",
"list": "white"
}'Respuesta
{
"uuid": "ip1",
"name": "Oficina",
"ip_range": "203.0.113.0/24",
"list": "white"
}/api/v1/ips/{uuid}/Obtener una regla de IP.
Parámetros de ruta
| uuid* | string | UUID de la regla. |
Petición
curl -X GET "https://api.passfortress.com/api/v1/ips/{uuid}/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"uuid": "ip1",
"name": "Oficina",
"ip_range": "203.0.113.0/24",
"list": "white"
}/api/v1/ips/{uuid}/Eliminar una regla de IP.
Devuelve 204 No Content sin cuerpo.
Parámetros de ruta
| uuid* | string | UUID de la regla. |
Petición
curl -X DELETE "https://api.passfortress.com/api/v1/ips/{uuid}/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
"204 No Content (sin cuerpo)"Contenedores
Carpetas para organizar los secretos.
/api/v1/containers//api/v1/containers//api/v1/containers/{uuid}//api/v1/containers/{uuid}//api/v1/containers/{uuid}//api/v1/containers/{uuid}//api/v1/containers/Listar contenedores.
Petición
curl -X GET "https://api.passfortress.com/api/v1/containers/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"uuid": "f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff",
"name": "Trabajo",
"description": "",
"secrets_count": 42
}
]
}/api/v1/containers/Crear un contenedor.
Cuerpo (JSON)
| name* | string | Nombre del contenedor. |
| description | string | Descripción (opcional). |
Petición
curl -X POST "https://api.passfortress.com/api/v1/containers/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Trabajo",
"description": ""
}'Respuesta
{
"uuid": "f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff",
"name": "Trabajo",
"description": "",
"secrets_count": 0
}/api/v1/containers/{uuid}/Obtener un contenedor.
Parámetros de ruta
| uuid* | string | UUID del contenedor. |
Petición
curl -X GET "https://api.passfortress.com/api/v1/containers/{uuid}/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"uuid": "f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff",
"name": "Trabajo",
"description": "",
"secrets_count": 42
}/api/v1/containers/{uuid}/Reemplazar un contenedor.
Parámetros de ruta
| uuid* | string | UUID del contenedor. |
Cuerpo (JSON)
| name* | string | Nuevo nombre. |
| description | string | Nueva descripción. |
Petición
curl -X PUT "https://api.passfortress.com/api/v1/containers/{uuid}/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Trabajo (2026)",
"description": ""
}'Respuesta
{
"uuid": "f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff",
"name": "Trabajo (2026)",
"description": "",
"secrets_count": 42
}/api/v1/containers/{uuid}/Actualizar parcialmente un contenedor.
Parámetros de ruta
| uuid* | string | UUID del contenedor. |
Cuerpo (JSON)
| name | string | Nuevo nombre. |
| description | string | Nueva descripción. |
Petición
curl -X PATCH "https://api.passfortress.com/api/v1/containers/{uuid}/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Personal"
}'Respuesta
{
"uuid": "f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff",
"name": "Personal",
"description": "",
"secrets_count": 42
}/api/v1/containers/{uuid}/Eliminar un contenedor.
Devuelve 204 No Content sin cuerpo.
Parámetros de ruta
| uuid* | string | UUID del contenedor. |
Petición
curl -X DELETE "https://api.passfortress.com/api/v1/containers/{uuid}/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
"204 No Content (sin cuerpo)"Secretos
El núcleo de la API. Los METADATOS (nombre, tipo, URL, contenedores) no requieren master key; leer o escribir VALORES descifrados sí requiere la cabecera X-Master-Key.
/api/v1/secrets//api/v1/secrets//api/v1/secrets//api/v1/secrets/{uuid}//api/v1/secrets/{uuid}//api/v1/secrets/{uuid}//api/v1/secrets/{uuid}//api/v1/secrets/{uuid}/value//api/v1/secrets/{uuid}/history//api/v1/secrets/{uuid}/duplicate//api/v1/secrets/{uuid}/share//api/v1/secrets/{uuid}/download//api/v1/secrets/shared//api/v1/secrets/accept-shared//api/v1/secrets/import//api/v1/secrets/Listar secretos (solo metadatos).
Devuelve metadatos paginados (50 por página). Nunca incluye valores descifrados.
Parámetros de consulta
| type | string | password, envfile, totp o file. |
| container | string | UUID del contenedor para filtrar. |
| search | string | Búsqueda por nombre, URL o nombre de archivo. |
| page | integer | Número de página (50 por página). |
Petición
curl -X GET "https://api.passfortress.com/api/v1/secrets/?type=password&page=1" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"count": 673,
"next": null,
"previous": null,
"results": [
{
"uuid": "a1b2c3d4-0000-1111-2222-333344445555",
"secret_type": "password",
"name": "GitHub",
"file_name": null,
"url": "https://github.com",
"notes": "",
"available": true,
"shared": false,
"automatic_password_change": false,
"website": {
"uuid": "w1a2b3c4-0000-1111-2222-333344445555",
"hostname": "github.com",
"login_url": "https://github.com/login",
"automatic_password_change": false
},
"containers": [
{
"uuid": "f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff",
"name": "Trabajo",
"description": "",
"secrets_count": 42
}
],
"container_uuids": [
"f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff"
],
"identifiers": [
{
"uuid": "id1",
"key": "usuario",
"value": "ada@example.com"
}
],
"has_value": true,
"imported_at": null
}
]
}/api/v1/secrets/Crear un secreto con su primer valor.
Requiere X-Master-Key porque cifra el valor en el servidor. Devuelve los metadatos (201).
Cuerpo (JSON)
| secret_type* | string | password, envfile, totp o file. |
| name | string | Nombre del secreto. |
| value | string | Valor en claro para password/envfile (se cifra en el servidor). Para totp es la URI otpauth://totp/... completa (semilla base32 + issuer + algoritmo/dígitos/periodo); el servidor la guarda cifrada sin parsearla. |
| file_content_b64 | string | Contenido del archivo en base64 (tipo file). |
| file_name | string | Nombre del archivo (tipo file). |
| url | string | URL asociada. |
| notes | string | Notas. |
| containers | array | UUIDs de contenedores (alias: container_uuids). |
| identifiers | array | Pares {key, value}. |
Petición
curl -X POST "https://api.passfortress.com/api/v1/secrets/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "X-Master-Key: <tu-master-key>" \
-H "Content-Type: application/json" \
-d '{
"secret_type": "password",
"name": "GitHub",
"url": "https://github.com",
"value": "s3cr3t-p4ss",
"containers": [
"f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff"
],
"identifiers": [
{
"key": "usuario",
"value": "ada@example.com"
}
]
}'Respuesta
{
"uuid": "a1b2c3d4-0000-1111-2222-333344445555",
"secret_type": "password",
"name": "GitHub",
"file_name": null,
"url": "https://github.com",
"notes": "",
"available": true,
"shared": false,
"automatic_password_change": false,
"website": {
"uuid": "w1a2b3c4-0000-1111-2222-333344445555",
"hostname": "github.com",
"login_url": "https://github.com/login",
"automatic_password_change": false
},
"containers": [
{
"uuid": "f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff",
"name": "Trabajo",
"description": "",
"secrets_count": 42
}
],
"container_uuids": [
"f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff"
],
"identifiers": [
{
"uuid": "id1",
"key": "usuario",
"value": "ada@example.com"
}
],
"has_value": true,
"imported_at": null
}/api/v1/secrets/Crear un autenticador TOTP (segundo factor).
Mismo endpoint que crear cualquier secreto, con secret_type: "totp". El value es la URI otpauth://totp/... completa (semilla base32 + issuer + algoritmo/dígitos/periodo). El servidor la cifra bajo la master key sin parsearla (conocimiento cero): NUNCA calcula códigos. El código de 6 dígitos se genera en el CLIENTE tras leer el valor. Compatible con el estándar TOTP (RFC 6238). Requiere X-Master-Key.
Cuerpo (JSON)
| secret_type* | string | Debe ser "totp". |
| name | string | Etiqueta visible (p. ej. el issuer del servicio). |
| value* | string | URI otpauth://totp/... completa (se guarda cifrada sin parsearse). |
| url | string | URL asociada. |
| notes | string | Notas. |
| containers | array | UUIDs de contenedores (alias: container_uuids). |
| identifiers | array | Pares {key, value} (p. ej. la cuenta). |
Petición
curl -X POST "https://api.passfortress.com/api/v1/secrets/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "X-Master-Key: <tu-master-key>" \
-H "Content-Type: application/json" \
-d '{
"secret_type": "totp",
"name": "GitHub",
"value": "otpauth://totp/GitHub:ada@example.com?secret=JBSWY3DPEHPK3PXP&issuer=GitHub&algorithm=SHA1&digits=6&period=30",
"url": "https://github.com",
"identifiers": [
{
"key": "account",
"value": "ada@example.com"
}
]
}'Respuesta
{
"uuid": "a1b2c3d4-0000-1111-2222-333344445555",
"secret_type": "totp",
"name": "GitHub",
"file_name": null,
"url": "https://github.com",
"notes": "",
"available": true,
"shared": false,
"automatic_password_change": false,
"website": {
"uuid": "w1a2b3c4-0000-1111-2222-333344445555",
"hostname": "github.com",
"login_url": "https://github.com/login",
"automatic_password_change": false
},
"containers": [
{
"uuid": "f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff",
"name": "Trabajo",
"description": "",
"secrets_count": 42
}
],
"container_uuids": [
"f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff"
],
"identifiers": [
{
"uuid": "id1",
"key": "usuario",
"value": "ada@example.com"
}
],
"has_value": true,
"imported_at": null
}/api/v1/secrets/{uuid}/Obtener los metadatos de un secreto.
Parámetros de ruta
| uuid* | string | UUID del secreto. |
Petición
curl -X GET "https://api.passfortress.com/api/v1/secrets/{uuid}/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"uuid": "a1b2c3d4-0000-1111-2222-333344445555",
"secret_type": "password",
"name": "GitHub",
"file_name": null,
"url": "https://github.com",
"notes": "",
"available": true,
"shared": false,
"automatic_password_change": false,
"website": {
"uuid": "w1a2b3c4-0000-1111-2222-333344445555",
"hostname": "github.com",
"login_url": "https://github.com/login",
"automatic_password_change": false
},
"containers": [
{
"uuid": "f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff",
"name": "Trabajo",
"description": "",
"secrets_count": 42
}
],
"container_uuids": [
"f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff"
],
"identifiers": [
{
"uuid": "id1",
"key": "usuario",
"value": "ada@example.com"
}
],
"has_value": true,
"imported_at": null
}/api/v1/secrets/{uuid}/Reemplazar un secreto.
Requiere X-Master-Key (cifra el valor en el servidor). El tipo no se puede cambiar.
Parámetros de ruta
| uuid* | string | UUID del secreto. |
Cuerpo (JSON)
| name | string | Nombre. |
| value | string | Nuevo valor (cifra en el servidor). |
| url | string | URL asociada. |
| notes | string | Notas. |
| containers | array | UUIDs de contenedores. |
| identifiers | array | Pares {key, value}. |
Petición
curl -X PUT "https://api.passfortress.com/api/v1/secrets/{uuid}/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "X-Master-Key: <tu-master-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub",
"value": "nuevo-valor"
}'Respuesta
{
"uuid": "a1b2c3d4-0000-1111-2222-333344445555",
"secret_type": "password",
"name": "GitHub",
"file_name": null,
"url": "https://github.com",
"notes": "",
"available": true,
"shared": false,
"automatic_password_change": false,
"website": {
"uuid": "w1a2b3c4-0000-1111-2222-333344445555",
"hostname": "github.com",
"login_url": "https://github.com/login",
"automatic_password_change": false
},
"containers": [
{
"uuid": "f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff",
"name": "Trabajo",
"description": "",
"secrets_count": 42
}
],
"container_uuids": [
"f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff"
],
"identifiers": [
{
"uuid": "id1",
"key": "usuario",
"value": "ada@example.com"
}
],
"has_value": true,
"imported_at": null
}/api/v1/secrets/{uuid}/Actualizar parcialmente un secreto.
Requiere X-Master-Key (la escritura cifra en el servidor).
Parámetros de ruta
| uuid* | string | UUID del secreto. |
Cuerpo (JSON)
| name | string | Nombre. |
| value | string | Nuevo valor. |
Petición
curl -X PATCH "https://api.passfortress.com/api/v1/secrets/{uuid}/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "X-Master-Key: <tu-master-key>" \
-H "Content-Type: application/json" \
-d '{
"value": "nuevo-valor"
}'Respuesta
{
"uuid": "a1b2c3d4-0000-1111-2222-333344445555",
"secret_type": "password",
"name": "GitHub",
"file_name": null,
"url": "https://github.com",
"notes": "",
"available": true,
"shared": false,
"automatic_password_change": false,
"website": {
"uuid": "w1a2b3c4-0000-1111-2222-333344445555",
"hostname": "github.com",
"login_url": "https://github.com/login",
"automatic_password_change": false
},
"containers": [
{
"uuid": "f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff",
"name": "Trabajo",
"description": "",
"secrets_count": 42
}
],
"container_uuids": [
"f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff"
],
"identifiers": [
{
"uuid": "id1",
"key": "usuario",
"value": "ada@example.com"
}
],
"has_value": true,
"imported_at": null
}/api/v1/secrets/{uuid}/Eliminar un secreto.
Devuelve 204 No Content sin cuerpo.
Parámetros de ruta
| uuid* | string | UUID del secreto. |
Petición
curl -X DELETE "https://api.passfortress.com/api/v1/secrets/{uuid}/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
"204 No Content (sin cuerpo)"/api/v1/secrets/{uuid}/value/Leer el valor descifrado de un secreto.
Descifra el valor en memoria usando la master key. Devuelve 428 si falta X-Master-Key y 403 si es incorrecta. Los intentos fallidos cuentan para el lockout: tras superar el umbral devuelve 429 master_key_locked con Retry-After. Para un secreto totp el value es la URI otpauth://...; el código de 6 dígitos se calcula en el cliente (el servidor no genera códigos).
Parámetros de ruta
| uuid* | string | UUID del secreto. |
Petición
curl -X GET "https://api.passfortress.com/api/v1/secrets/{uuid}/value/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "X-Master-Key: <tu-master-key>"Respuesta
{
"uuid": "a1b2c3d4-0000-1111-2222-333344445555",
"secret_type": "password",
"value": "s3cr3t-p4ss"
}/api/v1/secrets/{uuid}/history/Historial de valores descifrados.
Devuelve un array plano (sin paginación) de valores históricos descifrados.
Parámetros de ruta
| uuid* | string | UUID del secreto. |
Petición
curl -X GET "https://api.passfortress.com/api/v1/secrets/{uuid}/history/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "X-Master-Key: <tu-master-key>"Respuesta
[
{
"uuid": "hv1",
"value": "s3cr3t-p4ss",
"created_date_time": "2026-06-01T11:00:00Z"
},
{
"uuid": "hv2",
"value": "old-pass",
"created_date_time": "2026-01-12T09:30:00Z"
}
]/api/v1/secrets/{uuid}/duplicate/Duplicar un secreto.
Devuelve los metadatos de la copia (201). No requiere master key.
Parámetros de ruta
| uuid* | string | UUID del secreto a copiar. |
Petición
curl -X POST "https://api.passfortress.com/api/v1/secrets/{uuid}/duplicate/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"uuid": "nuevo-uuid",
"secret_type": "password",
"name": "GitHub (copia)",
"file_name": null,
"url": "https://github.com",
"notes": "",
"available": true,
"shared": false,
"automatic_password_change": false,
"website": {
"uuid": "w1a2b3c4-0000-1111-2222-333344445555",
"hostname": "github.com",
"login_url": "https://github.com/login",
"automatic_password_change": false
},
"containers": [
{
"uuid": "f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff",
"name": "Trabajo",
"description": "",
"secrets_count": 42
}
],
"container_uuids": [
"f1f2f3f4-aaaa-bbbb-cccc-ddddeeeeffff"
],
"identifiers": [
{
"uuid": "id1",
"key": "usuario",
"value": "ada@example.com"
}
],
"has_value": true,
"imported_at": null
}/api/v1/secrets/{uuid}/download/Descargar el archivo descifrado (tipo file).
Devuelve el archivo binario descifrado. Requiere X-Master-Key.
Parámetros de ruta
| uuid* | string | UUID del secreto. |
Petición
curl -X GET "https://api.passfortress.com/api/v1/secrets/{uuid}/download/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "X-Master-Key: <tu-master-key>"Respuesta (binaria)
<contenido binario del archivo>
/api/v1/secrets/import/Importar contraseñas desde un CSV de Chrome.
multipart/form-data con el CSV exportado de Chrome. Cifra cada valor; requiere X-Master-Key.
Petición
curl -X POST "https://api.passfortress.com/api/v1/secrets/import/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "X-Master-Key: <tu-master-key>" \
-F "file=@passwords.csv"Respuesta
{
"imported": 128,
"skipped": 3,
"errors": []
}Identificadores
Pares clave-valor asociados a un secreto (p. ej. usuario, email, nota).
/api/v1/identifiers//api/v1/identifiers//api/v1/identifiers/{uuid}//api/v1/identifiers/{uuid}//api/v1/identifiers/{uuid}//api/v1/identifiers/{uuid}//api/v1/identifiers/Listar identificadores.
Petición
curl -X GET "https://api.passfortress.com/api/v1/identifiers/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"uuid": "id1",
"secret": "a1b2c3d4-0000-1111-2222-333344445555",
"key": "usuario",
"value": "ada@example.com"
}
]
}/api/v1/identifiers/Crear un identificador.
Cuerpo (JSON)
| secret* | string | UUID del secreto. |
| key | string | Clave. |
| value* | string | Valor. |
Petición
curl -X POST "https://api.passfortress.com/api/v1/identifiers/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"secret": "a1b2c3d4-0000-1111-2222-333344445555",
"key": "usuario",
"value": "ada@example.com"
}'Respuesta
{
"uuid": "id1",
"secret": "a1b2c3d4-0000-1111-2222-333344445555",
"key": "usuario",
"value": "ada@example.com"
}/api/v1/identifiers/{uuid}/Obtener un identificador.
Parámetros de ruta
| uuid* | string | UUID del identificador. |
Petición
curl -X GET "https://api.passfortress.com/api/v1/identifiers/{uuid}/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"uuid": "id1",
"secret": "a1b2c3d4-0000-1111-2222-333344445555",
"key": "usuario",
"value": "ada@example.com"
}/api/v1/identifiers/{uuid}/Reemplazar un identificador.
Parámetros de ruta
| uuid* | string | UUID del identificador. |
Cuerpo (JSON)
| key | string | Clave. |
| value* | string | Valor. |
Petición
curl -X PUT "https://api.passfortress.com/api/v1/identifiers/{uuid}/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"key": "usuario",
"value": "ada.lovelace@example.com"
}'Respuesta
{
"uuid": "id1",
"secret": "a1b2c3d4-0000-1111-2222-333344445555",
"key": "usuario",
"value": "ada.lovelace@example.com"
}/api/v1/identifiers/{uuid}/Actualizar parcialmente un identificador.
Parámetros de ruta
| uuid* | string | UUID del identificador. |
Cuerpo (JSON)
| value | string | Nuevo valor. |
Petición
curl -X PATCH "https://api.passfortress.com/api/v1/identifiers/{uuid}/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"value": "nuevo-valor"
}'Respuesta
{
"uuid": "id1",
"secret": "a1b2c3d4-0000-1111-2222-333344445555",
"key": "usuario",
"value": "nuevo-valor"
}/api/v1/identifiers/{uuid}/Eliminar un identificador.
Devuelve 204 No Content sin cuerpo.
Parámetros de ruta
| uuid* | string | UUID del identificador. |
Petición
curl -X DELETE "https://api.passfortress.com/api/v1/identifiers/{uuid}/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
"204 No Content (sin cuerpo)"Sitios web
Sitios web vinculados a los secretos (para favicons y autocompletado).
/api/v1/websites/Listar sitios web.
Petición
curl -X GET "https://api.passfortress.com/api/v1/websites/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"uuid": "w1a2b3c4-0000-1111-2222-333344445555",
"hostname": "github.com",
"login_url": "https://github.com/login",
"automatic_password_change": false
}
]
}/api/v1/websites/Crear un sitio web.
Cuerpo (JSON)
| hostname* | string | Host del sitio (p. ej. github.com). |
| login_url | string | URL de inicio de sesión (opcional). |
| automatic_password_change | boolean | Soporta cambio automático de contraseña. |
Petición
curl -X POST "https://api.passfortress.com/api/v1/websites/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"hostname": "github.com",
"login_url": "https://github.com/login"
}'Respuesta
{
"uuid": "w1a2b3c4-0000-1111-2222-333344445555",
"hostname": "github.com",
"login_url": "https://github.com/login",
"automatic_password_change": false
}API keys
Gestión programática de las propias API keys. Normalmente se administran desde el panel web; usar estos endpoints requiere el scope apikeys:*.
/api/v1/api-keys//api/v1/api-keys//api/v1/api-keys/{public_id}//api/v1/api-keys/{public_id}/revoke//api/v1/api-keys/{public_id}/rotate//api/v1/api-keys/Listar las API keys.
Petición
curl -X GET "https://api.passfortress.com/api/v1/api-keys/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"public_id": "key_abc123",
"name": "CI deploy",
"prefix": "pf_live_",
"last4": "a1b2",
"masked": "pf_live_…a1b2",
"scopes": [
"secrets:read",
"values:read"
],
"is_active": true,
"expires_at": null,
"last_used_at": "2026-06-20T08:00:00Z",
"created_at": "2026-05-01T10:00:00Z",
"revoked_at": null,
"rate_tier": "default",
"ip_allowlist": [],
"environment": "live"
}
]
}/api/v1/api-keys/Crear una API key (el token se muestra una sola vez).
La respuesta añade el campo token (texto plano) que NO se vuelve a mostrar.
Cuerpo (JSON)
| name* | string | Nombre descriptivo. |
| scopes | array | Lista de scopes. |
| environment | string | live o test. |
| rate_tier | string | default, high o unlimited. |
| expires_at | string | Fecha de expiración ISO-8601 (opcional). |
| ip_allowlist | array | Allowlist de IPs/CIDR (opcional). |
Petición
curl -X POST "https://api.passfortress.com/api/v1/api-keys/" \
-H "Authorization: Bearer pf_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "CI deploy",
"scopes": [
"secrets:read",
"values:read"
],
"environment": "live",
"rate_tier": "default"
}'Respuesta
{
"public_id": "key_abc123",
"name": "CI deploy",
"prefix": "pf_live_",
"last4": "a1b2",
"masked": "pf_live_…a1b2",
"scopes": [
"secrets:read",
"values:read"
],
"is_active": true,
"expires_at": null,
"last_used_at": null,
"created_at": "2026-05-01T10:00:00Z",
"revoked_at": null,
"rate_tier": "default",
"ip_allowlist": [],
"environment": "live",
"token": "pf_live_3f9a2c8e7b1d4f0a6c5e9d8b7a1f2e3c"
}/api/v1/api-keys/{public_id}/Obtener una API key.
Parámetros de ruta
| public_id* | string | Identificador público de la clave. |
Petición
curl -X GET "https://api.passfortress.com/api/v1/api-keys/{public_id}/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"public_id": "key_abc123",
"name": "CI deploy",
"prefix": "pf_live_",
"last4": "a1b2",
"masked": "pf_live_…a1b2",
"scopes": [
"secrets:read",
"values:read"
],
"is_active": true,
"expires_at": null,
"last_used_at": "2026-06-20T08:00:00Z",
"created_at": "2026-05-01T10:00:00Z",
"revoked_at": null,
"rate_tier": "default",
"ip_allowlist": [],
"environment": "live"
}/api/v1/api-keys/{public_id}/revoke/Revocar una API key.
Devuelve la clave actualizada (is_active = false, revoked_at fijado).
Parámetros de ruta
| public_id* | string | Identificador público de la clave. |
Petición
curl -X POST "https://api.passfortress.com/api/v1/api-keys/{public_id}/revoke/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"public_id": "key_abc123",
"name": "CI deploy",
"prefix": "pf_live_",
"last4": "a1b2",
"masked": "pf_live_…a1b2",
"scopes": [
"secrets:read",
"values:read"
],
"is_active": false,
"expires_at": null,
"last_used_at": "2026-06-20T08:00:00Z",
"created_at": "2026-05-01T10:00:00Z",
"revoked_at": "2026-06-26T12:00:00Z",
"rate_tier": "default",
"ip_allowlist": [],
"environment": "live"
}/api/v1/api-keys/{public_id}/rotate/Rotar una API key (genera un token nuevo).
Devuelve los campos de lectura más un token de un solo uso.
Parámetros de ruta
| public_id* | string | Identificador público de la clave. |
Petición
curl -X POST "https://api.passfortress.com/api/v1/api-keys/{public_id}/rotate/" \
-H "Authorization: Bearer pf_live_xxx"Respuesta
{
"public_id": "key_abc123",
"name": "CI deploy",
"prefix": "pf_live_",
"last4": "c5e9",
"masked": "pf_live_…c5e9",
"scopes": [
"secrets:read",
"values:read"
],
"is_active": true,
"expires_at": null,
"last_used_at": "2026-06-20T08:00:00Z",
"created_at": "2026-05-01T10:00:00Z",
"revoked_at": null,
"rate_tier": "default",
"ip_allowlist": [],
"environment": "live",
"token": "pf_live_9d8b7a1f2e3c3f9a2c8e7b1d4f0a6c5e"
}Toda respuesta de error tiene la forma { "detail": "…", "code": "…" }. Usa el code (no el texto) para ramificar tu lógica.
| code | HTTP | Causa | Solución |
|---|---|---|---|
invalid_api_key | 401 | La API key no existe, está mal formada o el prefijo no corresponde al entorno. | Revisa la cabecera Authorization y usa una clave válida (pf_live_ / pf_test_). |
api_key_revoked | 401 | La clave fue revocada desde el panel o de forma automática tras acumular demasiados intentos fallidos de master key (auto-revoke por fuerza bruta). | Crea una nueva API key en /developers/api-keys. |
api_key_expired | 401 | La clave superó su fecha de expiración. | Rota la clave o crea una nueva sin expiración. |
ip_not_allowed | 401 | La petición proviene de una IP fuera del allowlist de la clave. | Añade la IP de origen al allowlist de la clave o emite la petición desde una IP permitida. |
insufficient_scope | 403 | La clave no tiene el scope que exige el endpoint. | Crea o rota la clave incluyendo el scope necesario (ver tabla de scopes). |
master_key_required | 428 | Operación sobre un valor sin la cabecera X-Master-Key. | Reenvía la petición incluyendo X-Master-Key con la master key del usuario. |
master_key_invalid | 403 | La master key enviada no valida contra el test_passwd del usuario. | Comprueba que la master key es exactamente la del propietario de la bóveda. |
master_key_locked | 429 | Demasiados intentos fallidos de master key (umbral por usuario, por defecto 10/hora); el endpoint queda bloqueado temporalmente con backoff exponencial. | Espera el tiempo indicado en la cabecera Retry-After y reintenta con la master key correcta (una validación correcta resetea el contador). |
not_authenticated | 401 | Falta toda credencial (ni API key ni JWT). | Incluye la cabecera Authorization: Bearer <token>. |
permission_denied | 403 | El recurso pertenece a otro usuario o la acción no está permitida. | Usa el uuid de un recurso que pertenezca al titular de la clave. |
not_found | 404 | El recurso no existe o no es visible para el titular. | Verifica el uuid / public_id de la ruta. |
validation_error | 400 | El cuerpo no cumple el esquema (campos faltantes o inválidos). | Revisa el detalle por campo en la respuesta y corrige el payload. |
invalid_rate_tier | 400 | Se intentó crear o rotar una clave con el scope values:read y el tier unlimited, combinación no permitida. | Elige el tier default o high para claves que puedan leer valores descifrados. |
weak_master_key | 400 | La nueva master key (en registro o cambio de master key) no cumple la política mínima: ≥8 caracteres, no toda idéntica, sin secuencias triviales ni claves comunes. | Elige una master key más fuerte. Las master keys existentes mantienen su validez hasta que las cambies. |
throttled | 429 | Se superó el límite de peticiones de la clave. | Respeta la cabecera Retry-After y reintenta tras la ventana; sube de tier si lo necesitas. |
428 vs 401
master_key_required es un 428, no un 401. Trátalo como "falta la master key", no como "sesión caducada": reintenta añadiendo la cabecera X-Master-Key, no renueves la API key.Mínimo privilegio
Concede a cada clave solo los scopes que necesita. Evita apikeys:write salvo en aprovisionamiento.
Rotación periódica
Rota las claves con regularidad y tras cualquier sospecha de filtración. La rotación invalida el token anterior al instante.
Allowlist de IPs
Restringe cada clave a las IPs de tu backend. Las peticiones desde otras IPs reciben ip_not_allowed.
Nunca en el navegador
No incrustes claves ni la master key en JavaScript de cliente, apps móviles ni repositorios.
Separa entornos
pf_test_ en desarrollo y CI, y pf_live_ solo en producción. Así una filtración en un entorno de pruebas no compromete los datos reales.