Skip to Content

Public API

API HTTP estable y versionada para que integres tu Nxar con apps propias — backends de portales de cliente, ecommerce, dispositivos IoT, conectores no-oficiales con Zapier / n8n, etc.

Dos namespaces:

  • /public/v1/data/... — CRUD sobre tus records (Contacts, Opportunities, custom entities). Reusa exactamente los permisos y sharing que ya configuraste en la UI.
  • /public/v1/custom/... — endpoints custom que vos definís apuntando a un Logic Component o a una automation, con API key o con firma HMAC (webhooks).

Quickstart

1. Habilitar el Public API

Desde la UI: Configuración → Integraciones y API → Endpoints → API Config y activá Habilitar API Pública para este tenant.

(Necesitás el permiso manage_endpoints — viene en el permission set “System Admin” por default.)

2. Crear una API key

En la misma pantalla, API Keys → Crear API key. Te pide:

  • Name: para identificar la key en la lista (ej. “Customer portal backend”).
  • Rate limit: requests/minuto que aceptás de esta key. Default 1000.
  • CORS allowed origins: si la key se va a usar desde un browser, listá los origins (uno por línea, ej. https://app.cliente.com). Vacío = sólo server-to-server.
  • IP allowlist: CIDRs IPv4 (uno por línea, ej. 1.2.3.0/24). Vacío = cualquier IP.

Al crear, el token se muestra una sola vez en un modal con un botón Copy. Guardalo ya en tu secrets manager — no se puede recuperar después (si lo perdés, hay que borrar la key y crear una nueva).

3. Hacer tu primer request

curl -H "Authorization: Bearer nxar_api_..." \ https://tu-tenant.nx-ar.com/public/v1/me

Si todo está bien, recibís:

{ "data": { "userId": "...", "email": "...", "name": "...", "isOwner": true, "abilities": [...] }, "meta": {} }

URL scheme

GET /public/v1/me → identidad del API key GET /public/v1/data/{entity} → list (cursor pagination) GET /public/v1/data/{entity}/{id} → read POST /public/v1/data/{entity} → create PATCH /public/v1/data/{entity}/{id} → partial update (merge JSONB) DELETE /public/v1/data/{entity}/{id} → delete POST /public/v1/custom/{slug} → corre una automation (trigger HTTP) o un Logic Component

{entity} es el API name del entity type, en singular y snake_case (contact, opportunity, account: el que aparece debajo de la etiqueta en Configuración → Entidades). Un plural (contacts) da 404.

Authentication

Authorization: Bearer nxar_api_<hex>

Cada request lleva una API key. La key resuelve a un user de tu tenant — las queries y mutations corren con los permisos de ese user (sharing rules, field-level permissions, todo aplica igual que en el web UI).

Response envelope

Todas las respuestas tienen el shape:

// éxito: { "data": ..., "meta": {} } // error: { "error": { "code": "VALIDATION", "message": "...", "details": {...} } }

Códigos de error comunes:

CodeHTTPCuándo
UNAUTHORIZED401Token inválido / revoked / expirado
FORBIDDEN403El user de la key no tiene permiso sobre el recurso
CORS_DENIED403El Origin del browser no está en cors_allowed_origins
IP_DENIED403El client IP no está en ip_allowlist
NOT_FOUND404Entity o record inexistente
RATE_LIMITED429Excediste el rate limit. Header Retry-After indica los segundos
VALIDATION400Body inválido
API_DISABLED503El admin apagó el toggle global

Rate limiting

Cada API key tiene su propio rate_limit_per_minute (default 1000). Cuando excedés, recibís 429 con headers:

Retry-After: 47 X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 47

Los mismos headers X-RateLimit-* se incluyen en todas las respuestas para que sepas cuánto budget te queda sin tener que esperar al 429.

Pagination

GET /public/v1/data/{entity} paginar con cursor:

curl ".../public/v1/data/contacts?limit=50" # response: { data: [...], meta: { count: 50, hasMore: true, nextCursor: "eyJ0cyI6IjI..." } } curl ".../public/v1/data/contacts?limit=50&cursor=eyJ0cyI6IjI..." # siguiente página

El nextCursor es un string opaco (base64url) — tratalo como un token, no parsees el contenido. Pasalo tal cual al siguiente request. Cuando hasMore es false, no hay más páginas y nextCursor viene null.

limit máximo: 200.

Por qué cursor (en lugar de ?page=2):

  • Consistente con escrituras concurrentes. Si entre páginas alguien inserta records, no se mezclan en tu paginado — los nuevos quedan delante del cursor (los ves en una página posterior, no duplicados).
  • Performance. Sin offset scan; cada página es index seek O(log n) contra (entity_type_id, created_at DESC, id DESC).

Internamente el cursor es compuesto (created_at, id) para romper empates determinísticamente cuando dos records tienen el mismo timestamp (puede pasar en bulk inserts).

Custom endpoints

Para exponer lógica custom (cálculo de envío, lookup contra un sistema externo, flow de creación de tickets, etc.) hay dos targets posibles:

Logic Component

Una función JS sandboxed. Más liviano — el component define un handler con input/output schemas, vos lo registrás como endpoint en Configuración → Integraciones y API → Endpoints → Endpoints y webhooks.

  1. Crear el component en Configuración → Constructor de app → Logic Components.
  2. Crear el endpoint con target = “Logic Component” y elegir el component.
curl -X POST \ -H "Authorization: Bearer nxar_api_..." \ -H "Content-Type: application/json" \ -d '{"weight_kg": 2.5, "destination_zip": "1414"}' \ https://tu-tenant.nx-ar.com/public/v1/custom/calculate-shipping

El body se pasa como inputs al component. La response es el return value del handler.

Automation

Un flow de automation declarativo (mismo builder visual que las automations de records). Más potente para lógica multi-paso que combine create_record, call_integration, condition, loop, send_email, etc.

  1. Crear la automation en Configuración → Automatización → Procesos → Nueva con el tipo Endpoint de API. Eso fija el trigger en http.request y muestra los editores de Inputs y Outputs (también editables después desde Configuración dentro del builder).
  2. Definí los inputs (lo que esperás en el body) y outputs (las variables que el flow va a setear y querés devolver). Cada uno con name + type (text | number | boolean | json / objeto / lista) + required?. Declará como JSON / objeto lo que llega como estructura: si lo declarás text, el flow recibe el objeto convertido a texto y los nodos que lo leen saltean en silencio.
  3. Diseñá el flow. Los inputs/outputs viven bajo dos namespaces fijos para no mezclarse con las variables del propio flow:
    • Inputs: referenciables como {{inputs.<name>}}. Cualquier nodo puede leerlos: condition, set_value, get_record, send_email, etc. El autocomplete los sugiere mientras tipeás.
    • Outputs: writables vía set_value con target = outputs y fieldPath = <name> (el engine soporta dot-path en object vars). También podés llenarlos desde el return de un custom_component mapeando sus fields a outputs.<name> con outputMappings. El bag outputs arranca vacío {}, así que se puede escribir desde el primer nodo sin estado previo. Cuando el flow termina, el handler lee outputs.<name> para cada field declarado en el schema y los devuelve al cliente como response.data.
  4. Crear el endpoint en Public API → Endpoints y webhooks y elegir la automation que acabás de crear en ¿Qué corre?.
curl -X POST \ -H "Authorization: Bearer nxar_api_..." \ -H "Content-Type: application/json" \ -d '{"subject": "Sin internet", "contact_email": "[email protected]"}' \ https://tu-tenant.nx-ar.com/public/v1/custom/create-support-case

Response:

{ "data": { "case_id": "abc-123-...", // variable seteada por el flow "ticket_number": "CASE-0042" // idem }, "meta": { "nodesExecuted": 4, "durationMs": 137 } }

Reglas de validación: el body se valida contra input_schema antes de correr el flow — campos required faltantes o tipos incorrectos devuelven 422 EXECUTION_ERROR con el mensaje Input validation failed: [...] (la lista dice qué campo falló). Outputs que el flow no setea aparecen como null en la response.

Identity (run as)

Por default, el custom endpoint corre con la identidad del dueño del API key que hizo el request. Eso significa que CASL (sharing, field permissions) filtra con esos permisos.

Si querés que el endpoint corra siempre con la identidad de un user específico (típico para service accounts cuando múltiples API keys deben converger en un mismo owner / sharing), configurá Correr como usuario (en Opciones avanzadas del diálogo del endpoint). La identidad efectiva pasa a ser ese user. El executionContext del component o automation sigue mandando si declara system o specific_user.

Webhooks con token secreto

Es el camino corto para recibir un webhook: probarlo con curl, conectar una herramienta no-code (Zapier, Make, n8n, un Apps Script) o un sistema propio que no sabe firmar requests.

  1. En Endpoints y webhooks → Nuevo endpoint, elegí el nombre en la URL, qué corre (una automation con trigger HTTP o un logic component) y dejá Token secreto en ¿Quién puede llamarlo?.
  2. Al crear, Nxar muestra una sola vez la URL, el token y un request de ejemplo. Copialos: después sólo vas a poder generar un token nuevo (que invalida el anterior en el acto).
  3. Quien llama manda el token en el header X-Webhook-Token (o como Authorization: Bearer …) y el cuerpo en JSON o application/x-www-form-urlencoded.
curl -X POST "https://tu-tenant.nx-ar.com/public/v1/custom/nuevo-lead" \ -H "Content-Type: application/json" \ -H "X-Webhook-Token: nxar_whk_…" \ -d '{"event":"lead.created","email":"[email protected]"}'

El cuerpo llega como los inputs de la automation ({{inputs.email}}) o del logic component. Un token ausente o equivocado devuelve 401 y no ejecuta nada.

Qué contesta lo elegís vos, en ¿Qué contesta? al crear o editar el endpoint:

  • El resultado de lo que corre — 200 con lo que devolvió la automation (lo que hayas escrito en outputs.*) o el logic component. Es la opción para un endpoint que responde una consulta.
  • Sólo el acuse de recibo — 202 con { received: true, event_id }. Es lo que espera un proveedor que reintenta: confirma que el aviso llegó, sin devolver datos.

Si algo falla, las dos opciones devuelven el error del flujo (por ejemplo 422), para que quien llama sepa que tiene que reintentar.

Reintentos: si quien llama puede reenviar la misma entrega, que mande un header Idempotency-Key con un identificador propio. Con ese header, el segundo recibo devuelve 200 con { duplicate: true } sin volver a correr nada; sin él, cada request es un evento nuevo. ⚠️ Ese segundo recibo no trae el resultado del primero (no se guarda), así que un cliente que necesita la respuesta no debería mandar esa cabecera.

Ver qué llegó: dejá prendido Registrar cada request (viene activado al crear) y mirá Audit Logs: cada entrega aparece con el cuerpo que mandó el emisor y la respuesta, etiquetada TOKEN.

Webhooks inbound firmados (HMAC)

Para los casos donde un servicio externo (un payment processor, un proveedor de comms, un sistema legacy del cliente) tiene que mandar eventos sin tener una API key tuya, el custom endpoint soporta autenticación HMAC: el provider firma cada request con un shared secret y Nxar valida la firma antes de disparar el component / automation.

En el diálogo Nuevo endpoint, en ¿Quién puede llamarlo? elegí Firmado por el proveedor (HMAC). Aparecen dos campos extra:

  • Logic component verificador: el Logic Component que valida la firma del provider (ver abajo). Los connectors instalables traen el suyo.
  • Secret del webhook: el secret que vas a compartir con el provider. Pegalo desde la UI del provider. Se guarda en el config del endpoint y queda redacted en la UI después de crear.

El endpoint queda en la misma URL que un endpoint normal — POST /public/v1/custom/<slug> — pero sin Authorization header. La auth es la firma del provider:

El formato exacto del header de firma depende del verifier — cada provider tiene su scheme. Ejemplo con un verifier simple que valida HMAC-SHA256-sobre-body (patrón común — Brevo, Postmark, GitHub, …):

SECRET="el-secret-que-pegaste-en-la-UI" BODY='{"event":"payment.confirmed","payment_id":"abc123","amount":1000}' SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}') curl -X POST "https://tu-tenant.nx-ar.com/public/v1/custom/my-webhook" \ -H "content-type: application/json" \ -H "x-signature: $SIG" \ -d "$BODY"

Para providers con scheme más complejo (ej. MercadoPago usa ts=<unix>,v1=<hex> con signed manifest), el verifier Logic Component implementa la lógica específica. Usá el connector oficial cuando exista.

Idempotency está incorporada: el verifier extrae un provider_event_id del body y Nxar dedupea por (endpoint, dedup_key) en la DB. Si el provider reenvía el mismo evento por retry, el segundo recibo devuelve 200 con { duplicate: true } sin volver a disparar el component / automation. Cero double-processing garantizado a nivel de constraint, no de cache.

En éxito el endpoint responde 202 Accepted con { received: true, event_id } — semánticamente correcto para webhooks (recibido + processing OK). En fallo del dispatch, propaga 4xx / 5xx para que el provider retry-ee; la próxima retry exitosa pasa por idempotency y no duplica.

Verifier como Logic Component: el campo Logic component verificador del diálogo lista los Logic Components de tu workspace. Un verifier recibe { rawBody, headers, config }, valida la firma con ctx.crypto.* (hmacSha256, safeCompare, checkTimestampFresh) y devuelve { providerEventId, eventKind, canonicalEvent } con ctx.success(...), o rechaza con ctx.fail(...). Si tu provider no tiene connector oficial, escribís el verifier en Constructor de app → Logic Components y lo elegís acá. Algunos providers no firman sus notificaciones: el patrón entonces es un verifier que valida la forma del payload y un hydrate que vuelve a consultar el recurso al provider antes de confiar.

Connectors instalables: para providers comunes (MercadoPago, y próximamente Mobbex / Pagos360), instalá el connector desde el Marketplace. Trae el Logic Component verifier, el custom endpoint configurado y la automation que normaliza y despacha los eventos. Solo pegás el webhook_secret, configurás la URL del webhook en el panel del provider y elegís qué procesa cada evento (Integraciones y connectors).

Audit log

El admin puede activar el audit log desde Integraciones y API → Endpoints → API Config. Dos toggles:

  • “Log requests to standard endpoints” (global del tenant) cubre /public/v1/data/* y /public/v1/me y también todos los early failures (401, 403, 429, 503) sin importar el endpoint. Útil para detectar probing / brute-force / clientes mal escritos.
  • Per-endpoint toggle en cada custom endpoint cubre los success de /public/v1/custom/<slug> específicamente.

Si prendés el global y dejás el per-endpoint off, igual se loguean los 4xx/5xx del custom endpoint — el admin que prendió “audit standard” no espera perderse errores en otros endpoints.

Los requests persistidos se conservan 30 días y son visibles desde Integraciones y API → Endpoints → Audit Logs, con filtros por usuario, endpoint (/data/*, /me, /custom/* o un endpoint específico) y estado (todos / solo errores), con el request y la response de cada uno.

Independientemente del toggle, cada request siempre emite un log estructurado (event: public_api.request) a stdout — el equipo de Nxar los ingestía para troubleshooting incluso si vos tenés el audit log apagado. Incluye también el caso donde el token presentado no existe (api_key_id: null + api_key_prefix = primeros 12 chars del token — útil para forensia sin exponer el secret completo).

Limitaciones de M1

  • Solo IPv4 en el IP allowlist (IPv6 retorna false). Si necesitás IPv6, dejá la lista vacía.
  • Rate limit es in-memory por proceso. Si Nxar escala a múltiples nodes, cada uno enforcea su propio budget — el efectivo puede exceder el configurado proporcionalmente.
  • No hay OpenAPI spec auto-generada todavía. Post-MVP.
  • No hay SDKs (TS / Python). Usá cualquier cliente HTTP.
Last updated on