niiko/developers

API pública · v1

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ó.

Una llave, dos permisosLa clave con su alcance, y el workspace que lo encendió.
Tres desenlacesHecho, rechazado con motivo, o esperando una firma.
Reintentar es seguroCon Idempotency-Key no se ejecuta dos veces.

En 30 segundos

Emite una clave en tu workspace, enciende el permiso de la acción, y llama.

curl
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 '{ … }'
TypeScript
npm install @niiko/sdk
Python
pip install niiko

Autenticación

Una clave del workspace, no una sesión de persona: autentica una integración, no a alguien. Authorization: Bearer nk_…. Nace acotada a unos alcances y con caducidad, y 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

Un contrato de dos desenlaces obliga a que el tercero se disfrace de otro, y el que se disfraza siempre es el que más se tarda en entender.

statuscódigoqué significa
done200Hecho. Trae el output.
pending_approval202Queda esperando una firma humana y puede terminar horas después. Te avisamos por webhook cuando se resuelva.
refused4xxNo se hizo, y reason dice por qué.

Webhooks

Registra un destino y te avisamos cuando algo se resuelve — sobre todo lo que quedó pending_approval, que por definición termina cuando ya colgaste.

La firma

Cada entrega trae Niiko-Signature con el instante dentro de lo firmado, no solo el cuerpo. Firmar solo el cuerpo deja una firma válida para siempre: quien capture una entrega puede repetirla mañana. Verifica que el instante sea reciente y que la firma case.

Reintentos

Si tu servidor no contesta 2xx, se reintenta con espera creciente. Un 4xx que no sea 408 ni 429 no se reintenta: significa que el destino está mal, no que esté ocupado. Tras muchos fallos seguidos, ese destino se apaga solo — y te lo decimos.

Versiones

Una acción lleva su versión en el contrato. Cuando una se retira sale con la cabecera Sunset y las fechas dentro, y el motivo es version_retired — no un 404. La diferencia importa: un 404 dice «no existe» y manda a buscar un error de escritura; un 410 dice «existió, ya no» y manda a leer el aviso.

Las acciones abiertas

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 — son dos decisiones distintas y las toma gente distinta.

Lo que mandas

campotiporeglas
submissionIdstring (uuid)obligatorio
source"web_form" | "referral" | "api" | "import" | "event" | "other"obligatorio
namestringopcionalmáx. 256
emailstring (email)opcionalmáx. 320
phonestringopcionalmáx. 40
fieldsmap<string, unknown>opcional
consentobjectopcional

Lo que recibes

campotiporeglas
outcome"created" | "existing" | "ambiguous" | "rejected"obligatorio
clientIdstring | nullobligatorio
reason"honeypot" | "invalid_email" | "disposable_email" | "invalid_identity" | nullobligatorio

Ejemplo

curl
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"
}'
TypeScript
import { Niiko } from "@niiko/sdk";

const niiko = new Niiko({ apiKey: process.env.NIIKO_API_KEY! });

const r = await niiko.createLead({
  submissionId: "01890a5d-ac96-774b-bcce-b302099a8057",
  source: "web_form",
}, crypto.randomUUID());

if (r.status === "done") console.log(r.output);
else if (r.status === "refused") console.error(r.reason);
Python
import os, uuid
from niiko import Niiko

niiko = Niiko(api_key=os.environ["NIIKO_API_KEY"])

r = niiko.create_lead(
    submissionId="01890a5d-ac96-774b-bcce-b302099a8057",
    source="web_form",
    idempotency_key=str(uuid.uuid4()),
)

print(r.output if r.status == "done" else r.reason)

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.

  • honeypot
  • invalid_email
  • disposable_email
  • invalid_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é.

motivocódigoqué significa
invalid_input400El payload no valida contra el contrato. Es lo único de esta lista que puedes arreglar tú solo.
unauthorized401La 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_off403El workspace no ha encendido esta capacidad. No es una prohibición: es una perilla que le toca a su dueño.
origin_denied403Este origen no puede ejercer esta acción, y no hay perilla que lo cambie.
not_autonomous409Necesita una aprobación y este camino no puede esperarla.
stale409El mundo cambió entre que lo pediste y que se iba a hacer. Vuelve a leer antes de reintentar.
duplicate409Ya pasó. Trae la clave y cómo terminó la primera vez. No se devuelve el resultado original: solo se guarda QUE ocurrió.
in_flight409Otra petición con la misma clave está corriendo ahora. Trae Retry-After.
not_exposed404Esa acción no está abierta al exterior. Puede existir dentro del producto; lo que no existe es su puerta.
version_retired410Esa versión ya se retiró. Sale con Sunset y las fechas dentro: existió, ya no.
rate_limited429Cupo por clave. Trae Retry-After.
internal_error500Un fallo nuestro. Es lo único de esta lista que no es una decisión ni un estado.

Clientes

Los dos salen del mismo manifiesto que esta página, en la misma corrida. No validan tu input —podrían, y sería peor: un cliente que valida puede discrepar del servidor, y la dirección probable del error es la peligrosa—. Lo que aportan es la forma: qué campos hay, cuáles son obligatorios y qué te puede contestar, en tu editor, antes de mandar nada.

TypeScriptnpm install @niiko/sdk
Pythonpip install niiko