# Atmos API — Referencia v1 > Documentación completa en texto de la API pública del CRM Atmos. Esta es la versión legible por máquinas de https://docs.atmos.com.mx/docs (la UI interactiva requiere JavaScript). ## API pública de Atmos REST sobre `/api/v1/*`. Autenticación M2M con un **token de API** (`at-api-…`) emitido por un administrador del portal. ### Autenticación Cada petición lleva el token en la cabecera: ``` Authorization: Bearer at-api-XXXX.sk_YYYY ``` (También se acepta `X-Api-Key: at-api-XXXX.sk_YYYY`.) El token tiene **su propio scope** (acciones CRUD por objeto + alcance de datos + acceso por campo), independiente del usuario que lo creó. Solo un administrador crea, edita o revoca tokens. ### Quickstart Listar contactos: ``` curl https://app.atmos.com.mx/api/v1/contacts \ -H "Authorization: Bearer at-api-XXXX.sk_YYYY" ``` Crear un contacto: ``` curl -X POST https://app.atmos.com.mx/api/v1/contacts \ -H "Authorization: Bearer at-api-XXXX.sk_YYYY" \ -H "Content-Type: application/json" \ -d '{"firstName":"Ada","email":"ada@example.com"}' ``` Los identificadores en URLs y payloads son **friendlyId** (C0001, N0002, …), nunca UUID. ### Paginación Las listas y las búsquedas devuelven una página y un cursor: ```json { "results": [ { "friendlyId": "C0001", "email": "ada@example.com" } ], "paging": { "next": { "after": "eyJjcmVhdGVkQXQiOiI…", "link": "https://app.atmos.com.mx/api/v1/contacts?after=eyJ…" } } } ``` Se pide la siguiente con `?after={cursor}`. **Cuando ya no hay más, `paging.next` no viene** — esa es la condición de fin, no un `results` vacío. El cursor va firmado: no se construye a mano. ### Cuota y límites Cada respuesta trae `X-RateLimit-Limit`, `X-RateLimit-Remaining` y `X-RateLimit-Reset`. Para consultarlo **sin gastar una llamada de negocio** —y no enterarte con el primer `429`: ``` GET https://app.atmos.com.mx/api/v1/usage ``` Devuelve el nombre del token, su **modo** (`live`/`test`), su **alcance de datos**, los **objetos** a los que da acceso y `usage` con lo que queda de la cubeta de ráfaga y de la diaria. `usage` puede venir en `null` si el contador de límites no está disponible: en ese caso guíate por las cabeceras `X-RateLimit-*` de cualquier respuesta. ### Reintentos e idempotencia Los lotes de **`create` y `upsert`** aceptan la cabecera **`Idempotency-Key`** (cualquier cadena única que elijas, p.ej. un UUID por lote): ``` POST https://app.atmos.com.mx/api/v1/contacts/batch/create Idempotency-Key: 7f1c… ``` Si se corta la red y **reintentas** con la misma clave, Atmos devuelve el resultado de la primera ejecución en vez de crear todo dos veces. La clave se recuerda **24 horas**. Alcance exacto, para que no te sorprenda: protege **reintentos secuenciales**, que es el caso real (falla la red, vuelves a intentar). Dos peticiones **simultáneas** con la misma clave sí pueden ejecutarse las dos — no hay bloqueo. No la uses para paralelizar el mismo lote. ### Tokens de prueba Un token puede crearse en **modo prueba** (`test`). Funciona igual, con su **propia cubeta de límites**, y sus llamadas quedan marcadas como de prueba. Sirve para desarrollar la integración sin ensuciar las métricas del token real. El modo se elige al crear el token y se consulta en `/v1/usage`. ### Servidor MCP (Model Context Protocol) Además de REST, Atmos habla **MCP**: un asistente (Claude Desktop, Cursor, Cosmos) se conecta y opera el CRM con las mismas reglas del token. No es una API paralela — por dentro corre el mismo motor, el mismo scope, el mismo alcance de datos y los mismos límites. Una llamada MCP cuenta como una llamada de API. - **Endpoint:** `https://app.atmos.com.mx/api/v1/mcp` (Streamable HTTP, JSON-RPC 2.0). - **Auth:** el mismo `Authorization: Bearer at-api-…`. - **Métodos:** `initialize`, `ping`, `tools/list`, `tools/call`. Las herramientas se **derivan del scope del token**: verás `list_`, `search_`, `get_`, `create_`, `update_` y `delete_` por cada objeto que el token tenga autorizado, y nada más — un token de solo lectura no expone herramientas de escritura, así que el asistente no puede ni intentarlo. Configuración típica en un cliente MCP: ```json { "mcpServers": { "atmos": { "url": "https://app.atmos.com.mx/api/v1/mcp", "headers": { "Authorization": "Bearer at-api-XXXX.sk_YYYY" } } } } ``` Los errores usan el envelope único con `correlationId` (ver "Códigos de error"), útil para soporte: identifica la llamada exacta. ### Integración web (pixel de seguimiento + formularios) Lo que casi siempre se quiere al conectar un sitio con Atmos. Nada de esto usa el token de API en el navegador. #### 1. Pixel de seguimiento Una etiqueta en el `` de todas las páginas del sitio: ```html ``` `data-workspace` es el **código de portal** (9 caracteres alfanuméricos); se ve en la URL del CRM. El script **no lleva token** y es seguro en el frontend: solo escribe, nunca lee datos. Captura automáticamente: vistas de página (con `referrer` y parámetros `utm_*`), tiempo en página, clicks y profundidad de scroll (mapas de calor), y un latido cada 30 s mientras la pestaña está activa. Respeta **Do Not Track** (si el navegador lo pide, no envía nada) y soporta **SPAs** (escucha `pushState`/`popstate`, no hace falta recargar). Los clicks sobre `input`, `textarea` y `select` se ignoran a propósito para no capturar datos personales. API de JavaScript disponible después de cargar: ```js // Vincula la sesión anónima con un contacto (llamar tras un login o un envío de formulario) window.Atmos.identify({ email: 'ada@example.com' }); // Evento propio window.Atmos.track('demo_solicitada', { plan: 'growth' }); ``` También se identifica solo si la URL trae `?atmos_ct={token}` (los enlaces de campañas de Atmos lo añaden), lo que atribuye la visita al contacto correcto. #### 2. Formularios Dos caminos, en orden de preferencia: **a) Incrustar el formulario de Atmos** — una línea, sin backend propio. El formulario se crea en el CRM (Formularios) y ya trae anti-bot, Turnstile, campos condicionales, deduplicado de contactos y disparo de automatizaciones: ```html
``` `data-form` es el **slug** del formulario (el mismo de su URL pública `app.atmos.com.mx/{portal}/forms/{slug}`). Se pueden incrustar varios en una misma página: el script monta todos los contenedores que tengan `data-form` + `data-workspace`. **b) Formulario propio del sitio** — cuando el diseño del formulario debe ser del sitio. El envío va **a tu servidor**, y tu servidor llama a esta API: ``` POST https://app.atmos.com.mx/api/v1/contacts Authorization: Bearer at-api-XXXX.sk_YYYY Content-Type: application/json {"firstName":"Ada","lastName":"Lovelace","email":"ada@example.com","phone":"+52...","source":"web"} ``` ⚠️ **El token `at-api-…` nunca va en el navegador.** Es un secreto M2M con el scope completo que le diste: publicado en el HTML o en un bundle de JavaScript, cualquiera puede leer y escribir en el portal. Debe vivir en una variable de entorno del servidor (route handler de Next.js, función serverless, backend propio) y el formulario postea a ese endpoint interno. Después de un envío exitoso, llama a `window.Atmos.identify({ email })` en el cliente para que la navegación anónima previa quede atribuida al contacto recién creado. ### Campos personalizados Un campo personalizado se usa **por su nombre interno**, igual que uno de fábrica. Si en Configuración → Campos creaste `servicio`, en la API es `servicio`. Sin prefijos ni envoltorios. Consulta los campos de un objeto —de fábrica y personalizados— con: ``` GET /api/v1/properties/contact ``` El `key` de cada campo es **exactamente** el nombre que se usa para escribirlo y leerlo: ``` POST /api/v1/contacts {"email":"ada@example.com","servicio":"Consultoría","presupuesto":15000} GET /api/v1/contacts/C0001 → {"friendlyId":"C0001","email":"ada@example.com","servicio":"Consultoría","presupuesto":15000,…} ``` Funcionan igual en `?fields=` (`?fields=email,servicio`), en la **búsqueda** y en la **importación**: ``` POST /api/v1/contacts/search {"filterGroups":[{"filters":[{"field":"servicio","operator":"eq","value":"Consultoría"}]}]} ``` Detalles a tener en cuenta: - **Tipos.** Un campo numérico devuelve un número; los de fecha, texto en formato `YYYY-MM-DD`. - **Operadores de búsqueda.** Texto y desplegables: `eq`, `neq`, `in`, `nin`, `contains`, `startsWith`. Números y fechas: además `gt`, `gte`, `lt`, `lte`, `between`. Todos aceptan `isNull`/`isNotNull`, que también cubren al registro que **no tiene** ese campo lleno. - **Nombres reservados.** No se puede crear un campo personalizado con el nombre de uno de fábrica del mismo objeto (`email`, `phone`, `status`…): un nombre identifica a un solo campo. - **Acceso.** Un campo restringido por permisos no se devuelve (o llega como `****` si así se configuró), y **filtrar por él responde 403** en vez de ignorar el filtro en silencio. ## Convenciones - Base de la API: `https://app.atmos.com.mx/api/v1` - OpenAPI 3.1 (JSON): `https://docs.atmos.com.mx/api/docs/openapi.json` - Servidor MCP (Streamable HTTP, JSON-RPC 2.0): `https://app.atmos.com.mx/api/v1/mcp` - Identificadores: **friendlyId** (`C0001` contacto, `E` empresa, `N` negocio, `A` actividad, `T` ticket, `P` proyecto, `F` factura). Nunca UUID. - Paginación por cursor: `?limit=&after=`; la respuesta trae `paging.next.after`. - Selección de campos: `?fields=firstName,email`. - Errores: envelope único `{ status, category, message, correlationId, errors[] }`. - Límites de tasa: cabeceras `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`. ## Endpoints ### CRM #### `GET /api/v1/{object}` Lista registros de un objeto. Uno de: contacts, companies, deals, activities, tickets, projects, invoices, products, quotes, lists, forms, o el api_name de un objeto personalizado (#84) del portal. - Parámetros de ruta: `object` - Query: `limit`, `after`, `fields` - operationId: `listRecords` #### `POST /api/v1/{object}` Crea un registro. Los CAMPOS válidos dependen del portal: consúltalos en GET /v1/properties/{objectType} — el `key` de cada uno es el nombre que va en el body (los personalizados, por su nombre interno, sin prefijo). Objetos escribibles: contacts, companies, deals, activities, tickets, projects, invoices. - Parámetros de ruta: `object` - operationId: `createRecord` #### `GET /api/v1/{object}/{id}` Obtiene un registro por friendlyId. - Parámetros de ruta: `object`, `id` - Query: `fields` - operationId: `getRecord` #### `PATCH /api/v1/{object}/{id}` Actualiza un registro por friendlyId (parcial: solo los campos enviados). Los campos válidos son los mismos de GET /v1/properties/{objectType}. - Parámetros de ruta: `object`, `id` - operationId: `updateRecord` #### `DELETE /api/v1/{object}/{id}` Envía un registro a la papelera (soft delete). - Parámetros de ruta: `object`, `id` - operationId: `deleteRecord` #### `POST /api/v1/{object}/search` Búsqueda con filterGroups (OR entre grupos, AND dentro). - Parámetros de ruta: `object` - Body: `filterGroups`, `sorts`, `fields`, `limit`, `after` - operationId: `searchRecords` #### `POST /api/v1/{object}/batch/{op}` Operación en lote (op: create|read|update|archive|upsert). 207 Multi-Status en éxito parcial. - Parámetros de ruta: `object`, `op` - Body: `inputs`, `idProperty` - operationId: `batchRecords` #### `POST /api/v1/{object}/{id}/permanent-delete` Borrado permanente / derecho al olvido (irreversible). Requiere { confirm: true }. - Parámetros de ruta: `object`, `id` - Body: `confirm` - operationId: `permanentDeleteRecord` #### `PUT /api/v1/{object}/{id}/associations/{toType}/{toId}` Vincula dos registros por su pivote nativo. - Parámetros de ruta: `object`, `id`, `toType`, `toId` - operationId: `associate` #### `DELETE /api/v1/{object}/{id}/associations/{toType}/{toId}` Elimina el vínculo entre dos registros. - Parámetros de ruta: `object`, `id`, `toType`, `toId` - operationId: `dissociate` ### Soporte #### `GET /api/v1/users` Lista los miembros del portal. - operationId: `listUsers` #### `GET /api/v1/teams` Lista los equipos del portal. - operationId: `listTeams` #### `GET /api/v1/owners` Lista los usuarios asignables (owners). - operationId: `listOwners` #### `GET /api/v1/pipelines` Lista los pipelines de negocios. - operationId: `listPipelines` #### `GET /api/v1/pipelines/{id}/stages` Lista las etapas de un pipeline. - Parámetros de ruta: `id` - operationId: `listStages` ### Properties #### `GET /api/v1/properties/{objectType}` Lista las definiciones de campo (default + custom) de un objeto. - Parámetros de ruta: `objectType` - operationId: `listProperties` #### `POST /api/v1/properties/{objectType}` Crea un campo personalizado. - Parámetros de ruta: `objectType` - operationId: `createProperty` #### `PATCH /api/v1/properties/{objectType}/{fieldKey}` Edita un campo personalizado. - Parámetros de ruta: `objectType`, `fieldKey` - operationId: `updateProperty` #### `DELETE /api/v1/properties/{objectType}/{fieldKey}` Elimina un campo personalizado. - Parámetros de ruta: `objectType`, `fieldKey` - operationId: `deleteProperty` ### Webhooks #### `GET /api/v1/webhooks` Lista las suscripciones de webhooks. - operationId: `listWebhooks` #### `POST /api/v1/webhooks` Crea una suscripción de webhook. Eventos válidos: contact.created, contact.updated, contact.deleted, company.created, company.updated, company.deleted, deal.created, deal.updated, deal.deleted, activity.created, activity.updated, activity.deleted, ticket.created, ticket.updated, ticket.deleted, project.created, project.updated, project.deleted, invoice.created, invoice.updated, invoice.deleted, product.created, product.updated, product.deleted, quote.created, quote.updated, quote.deleted, list.created, list.updated, list.deleted, form.created, form.updated, form.deleted. - Body: `url`, `events`, `active` - operationId: `createWebhook` #### `GET /api/v1/webhooks/{id}` Obtiene una suscripción. - Parámetros de ruta: `id` - operationId: `getWebhook` #### `PATCH /api/v1/webhooks/{id}` Edita una suscripción. - Parámetros de ruta: `id` - Body: `url`, `events`, `active` - operationId: `updateWebhook` #### `DELETE /api/v1/webhooks/{id}` Elimina una suscripción. - Parámetros de ruta: `id` - operationId: `deleteWebhook` #### `GET /api/v1/webhooks/{id}/deliveries` Log de entregas de una suscripción. - Parámetros de ruta: `id` - operationId: `listDeliveries` #### `POST /api/v1/webhooks/{id}/deliveries/{deliveryId}/replay` Reintenta una entrega. - Parámetros de ruta: `id`, `deliveryId` - operationId: `replayDelivery` ### Import #### `POST /api/v1/imports` Inicia una importación (JSON rows). Objetos: contacts, companies, deals, activities, tickets, projects, invoices. - Body: `objectType`, `rows`, `csv`, `mode`, `dateFormat` - operationId: `startImport` #### `GET /api/v1/imports` Lista las importaciones. - operationId: `listImports` #### `GET /api/v1/imports/{id}` Estado/progreso de una importación. - Parámetros de ruta: `id` - operationId: `getImport` #### `DELETE /api/v1/imports/{id}` Cancela una importación en curso. - Parámetros de ruta: `id` - operationId: `cancelImport` ### Token #### `GET /api/v1/usage` Consumo y cuota RESTANTE del token que hace la llamada: ráfaga y diaria, modo (live/test) y objetos a los que da acceso. Sirve para anticipar un 429 en vez de descubrirlo. - operationId: `getUsage` #### `POST /api/v1/mcp` Servidor MCP (Model Context Protocol) sobre Streamable HTTP, JSON-RPC 2.0: `initialize`, `ping`, `tools/list`, `tools/call`. Mismo token, mismo scope y mismos límites que REST. Ver la sección "Servidor MCP" de esta doc. - operationId: `mcpEndpoint` ## Códigos de error - `400 VALIDATION_ERROR` — datos inválidos (detalle por campo en `errors[]`). - `401 UNAUTHORIZED` — token ausente, inválido o revocado. - `403 FORBIDDEN` — el token no tiene esa acción, ese objeto o ese campo en su scope. - `404 NOT_FOUND` — el friendlyId no existe en el portal del token. - `409 CONFLICT` — conflicto de estado (p.ej. duplicado en upsert). - `429 RATE_LIMITED` — límite de tasa excedido; reintentar según `X-RateLimit-Reset`.