Saltar al contenido

OAuth para apps

Tu app se conecta a la cuenta de Cord de un negocio con un clic, sin que nadie copie una llave.

Cuándo usarlo

Si construyes una app que muchos negocios van a conectar con su Cord —un conector, un complemento, una plataforma de automatización—, usa OAuth 2.0 en lugar de pedirles una llave de API. La persona autoriza en una pantalla de Cord, elige el espacio de trabajo y puede revocar el acceso cuando quiera. Es el mismo mecanismo que usan las apps de Cord en Zapier y Make, y el complemento de Cord para Gmail.

Para una integración de tu propio negocio, una llave de API es más simple.

Registrar tu app

El registro de clientes OAuth todavía no es de autoservicio. Escríbenos a contacto@cordhq.app con el nombre de tu app, su dominio y las direcciones de regreso (redirect_uri) exactas. Te damos un client_id y un client_secret. Guarda el secreto en tu servidor: Cord solo conserva su huella y no puede volver a mostrarlo.

Las direcciones de regreso deben ser https (o http://localhost para desarrollo) y se comparan exactas, sin comodines.

1. Pide la autorización

Manda a la persona a:

https://cordhq.app/oauth/authorize
  ?response_type=code
  &client_id=cord_oc_...
  &redirect_uri=https://tuapp.com/cord/callback
  &scope=write
  &state=VALOR_ALEATORIO
  &code_challenge=...            (opcional, PKCE)
  &code_challenge_method=S256    (opcional, PKCE)
Parámetro Detalle
scope read para solo lectura; write para lectura y escritura (incluye read). Sin scope, Cord pide read. Puedes separar valores con espacio, coma o +; cualquier otro valor responde invalid_scope.
state Un valor aleatorio que tu app verifica al regreso. Evita que un tercero te inyecte una autorización.
code_challenge PKCE: BASE64URL(SHA256(code_verifier)), 43 caracteres sin relleno. Recomendado siempre; solo se acepta code_challenge_method=S256.

Si la persona no tiene sesión, Cord le pide iniciarla y regresa a la misma pantalla. Solo quien tiene el permiso de Ajustes en un espacio de trabajo puede autorizar apps en él. La pantalla muestra qué podrá hacer tu app y qué no.

Al autorizar, Cord regresa a tu redirect_uri con ?code=...&state=.... Si la persona cancela, regresa con ?error=access_denied&state=....

2. Canjea el código

El código dura 5 minutos y sirve una sola vez. Canjéalo desde tu servidor:

curl -X POST "https://cordhq.app/api/oauth/token" \
     -d grant_type=authorization_code \
     -d code=cord_ac_... \
     -d redirect_uri=https://tuapp.com/cord/callback \
     -d client_id=cord_oc_... \
     -d client_secret=cord_cs_... \
     -d code_verifier=...

También puedes mandar las credenciales del cliente con autenticación HTTP Basic.

{ "access_token": "cord_at_...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "cord_rt_...", "scope": "write" }

3. Llama a la API

El access_token funciona como una llave en toda la API v1 y en el servidor MCP:

curl "https://cordhq.app/api/v1/me" -H "Authorization: Bearer cord_at_..."

4. Renueva el acceso

El access token dura 1 hora. Renuévalo con el refresh token, que dura 180 días:

curl -X POST "https://cordhq.app/api/oauth/token" \
     -d grant_type=refresh_token \
     -d refresh_token=cord_rt_... \
     -d client_id=cord_oc_... \
     -d client_secret=cord_cs_...

Cada renovación entrega un refresh token nuevo y el anterior deja de servir. Guarda siempre el nuevo. Si un refresh token ya usado se presenta de nuevo pasados 30 segundos, Cord lo trata como posible robo y revoca la conexión completa; la persona tendrá que volver a autorizar.

5. Revoca

curl -X POST "https://cordhq.app/api/oauth/revoke" \
     -d token=cord_rt_... -d client_id=cord_oc_... -d client_secret=cord_cs_...

Responde 200 aunque el token no exista, para no revelar qué tokens son válidos. La persona también puede revocar tu app en la pestaña Llaves del Cord Workbench, donde aparece como Conexión autorizada.

PKCE

PKCE evita que un código interceptado se pueda canjear. Genera un code_verifier aleatorio de 43 a 128 caracteres (A-Z, a-z, 0-9, -, ., _, ~), guárdalo en la sesión de tu servidor y manda su huella al autorizar:

import { createHash, randomBytes } from 'node:crypto';

const codeVerifier = randomBytes(48).toString('base64url'); // 64 caracteres
const codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url'); // 43 caracteres
// En /oauth/authorize: code_challenge=<codeChallenge>&code_challenge_method=S256
// En /api/oauth/token: code_verifier=<codeVerifier>

Si mandaste code_challenge al autorizar, el canje exige el code_verifier que le corresponde. Si no lo mandaste, el canje no debe llevar code_verifier: uno inesperado también responde invalid_grant.

Errores de la autorización

Hay dos clases de error en /oauth/authorize, y se comportan distinto a propósito.

Cord no regresa a tu app cuando no puede confiar en la dirección de regreso. La persona ve una página de error de Cord y nada se redirige:

Situación Lo que pasa
client_id que no existe Página de error de Cord (unknown_client).
redirect_uri que no coincide exacta con una registrada Página de error de Cord. Revisa mayúsculas, barra final y puerto.

Cord regresa a tu redirect_uri con error y tu state cuando el cliente y la dirección son válidos pero la petición no:

error Cuándo
unsupported_response_type response_type no es code.
invalid_scope scope trae un valor distinto de read o write.
invalid_request code_challenge mal formado, o code_challenge_method distinto de S256.
access_denied La persona canceló, o eligió un espacio de trabajo en el que no puede autorizar apps.
https://tuapp.com/cord/callback?error=invalid_scope&state=VALOR_ALEATORIO

Estas redirecciones no llevan error_description. Verifica siempre que state sea el que mandaste antes de mostrar el error.

Errores del canje y la renovación

POST /api/oauth/token acepta el cuerpo como application/x-www-form-urlencoded o como JSON, y responde siempre JSON con error y error_description:

{ "error": "invalid_grant", "error_description": "El código o el token no es válido, ya se usó o venció." }
Código error error_description Cuándo
401 invalid_client Las credenciales de la aplicación no son válidas. client_id o client_secret incorrectos, por Basic o en el cuerpo.
400 invalid_request El cuerpo de la petición no se pudo leer. El cuerpo no es un formulario ni JSON válido.
400 invalid_request Faltan code o redirect_uri. Canje sin code o sin redirect_uri.
400 invalid_request Falta refresh_token. Renovación sin refresh_token.
400 invalid_request Este espacio ya tiene demasiadas conexiones de esta aplicación… El espacio de trabajo ya tiene 25 conexiones vivas de tu app. La persona debe revocar las que no use.
400 unsupported_grant_type Solo se admite authorization_code y refresh_token. Cualquier otro grant_type.
400 invalid_grant El código o el token no es válido, ya se usó o venció. Código vencido o usado, redirect_uri distinta a la de la autorización, code_verifier que no cuadra, o refresh token vencido, usado o de otro cliente.
429 — — Más de 300 peticiones por minuto desde la misma IP. Respeta Retry-After.

error_description está en español y puede cambiar: decide por error.

POST /api/oauth/revoke responde 401 con error: "invalid_client" si las credenciales del cliente no son válidas, y 200 en cualquier otro caso.

Cuota

Las llamadas con un token de OAuth cuentan contra la cuota de API del plan del negocio, igual que una llave. La única excepción es el complemento de Cord para Gmail, que es una superficie de la propia Cord: sus llamadas no consumen la cuota mensual, aunque sí los límites por minuto.