API de acciones
Expone verbos, no filas. No lista clientes, no lee facturas, no publica tablas.
Lo único que sabe hacer es ejercer una acción declarada como pública, con la autorización que su dueño
encendió, y contarte con precisión qué pasó.
Autenticación
Una clave del workspace, no una sesión de persona: autentica una integración.
Authorization: Bearer nk_…. La clave nace acotada a unos alcances y con caducidad; se ve una sola
vez, al crearla.
Idempotencia
Manda Idempotency-Key con un valor único por encargo. Un reintento por timeout con
la misma clave no vuelve a ejecutar: contesta duplicate con lo que pasó la
primera vez. Sin esa cabecera, un reintento es un encargo nuevo. La protección dura 24 h.
Tres desenlaces, no dos
done — hecho. refused — no se hizo, y por qué.
pending_approval (202) — queda esperando una firma y puede terminar horas
después; te avisamos por webhook cuando se resuelva. Un contrato de dos desenlaces obliga a que el tercero se
disfrace de otro.
miira.lead_create v1
POST /api/v1/actions/miira.lead_create
- Alcance de la clave
miira.lead_create@1
- Permiso del workspace
api.miira.lead_create — nace apagado
Hacen falta los dos: una clave con ese alcance, y que el dueño del workspace
haya encendido ese permiso. Tener la clave no basta.
Lo que mandas
| campo | tipo | | reglas |
|---|
submissionId | string (uuid) | obligatorio | |
source | "web_form" | "referral" | "api" | "import" | "event" | "other" | obligatorio | |
name | string | opcional | máx. 256 |
email | string (email) | opcional | máx. 320 |
phone | string | opcional | máx. 40 |
fields | map<string, unknown> | opcional | |
consent | object | opcional | |
Lo que recibes
| campo | tipo | | reglas |
|---|
outcome | "created" | "existing" | "ambiguous" | "rejected" | obligatorio | |
clientId | string | null | obligatorio | |
reason | "honeypot" | "invalid_email" | "disposable_email" | "invalid_identity" | null | obligatorio | |
Ejemplo
curl -X POST https://niiko.org/api/v1/actions/miira.lead_create \
-H "Authorization: Bearer nk_…" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"submissionId": "01890a5d-ac96-774b-bcce-b302099a8057",
"source": "web_form"
}'
Motivos por los que puede no hacerse
Del vocabulario que esta acción declara. Un motivo fuera de esta lista es un fallo del
servidor, no un estado del producto.
honeypotinvalid_emaildisposable_emailinvalid_identity
Los motivos, y qué hacer con cada uno
El código HTTP lo elige el remedio, no el parecido: quién tiene que hacer
algo, y qué.
| motivo | código | qué significa |
invalid_input | 400 | El payload no valida contra el contrato. Es lo único de esta lista que puedes arreglar tú solo. |
unauthorized | 401 | La clave no vale. No se distingue entre inexistente, revocada, vencida o sin el alcance: distinguirlo convertiría esta puerta en un oráculo para quien esté probando claves. El estado exacto de tu clave lo ves en tu panel. |
grant_off | 403 | El workspace no ha encendido esta capacidad. No es una prohibición: es una perilla que le toca a su dueño. |
origin_denied | 403 | Este origen no puede ejercer esta acción, y no hay perilla que lo cambie. |
not_autonomous | 409 | Necesita una aprobación y este camino no puede esperarla. |
stale | 409 | El mundo cambió entre que lo pediste y que se iba a hacer. Vuelve a leer antes de reintentar. |
duplicate | 409 | Ya pasó. Trae la clave y cómo terminó la primera vez. No se devuelve el resultado original: solo se guarda QUE ocurrió. |
in_flight | 409 | Otra petición con la misma clave está corriendo ahora. Trae Retry-After. |
not_exposed | 404 | Esa acción no está abierta al exterior. Puede existir dentro del producto; lo que no existe es su puerta. |
version_retired | 410 | Esa versión ya se retiró. Sale con Sunset y las fechas dentro: existió, ya no. |
rate_limited | 429 | Cupo por clave. Trae Retry-After. |
internal_error | 500 | Un fallo nuestro. Es lo único de esta lista que no es una decisión ni un estado. |