Docs / REST API / Integration Guide

Backend Integration

Esta guΓ­a describe cΓ³mo integrar tu servicio con nuestra API RESTful. DiseΓ±ada para ser intuitiva y predecible, nuestra API utiliza caracterΓ­sticas estΓ‘ndar de HTTP como verbos, cΓ³digos de respuesta y autenticaciΓ³n basada en tokens. EncontrarΓ‘s ejemplos prΓ‘cticos de cURL, Python y JavaScript listos para copiar y pegar en tu dΓ­a a dΓ­a.

Authentication Required

La API soporta multiples esquemas de autenticacion. El metodo principal es Bearer Token via el header Authorization, pero tambien puedes usar API Keys y OAuth2 segun el caso de uso.

Bearer Token (JWT)

Envia tu token en cada peticion con el prefijo Bearer:

bash
curl https://api.cookbooks.dev/v1/projects \
  -H "Authorization: Bearer sk_test_51Mz..." \
  -H "Content-Type: application/json"

Flujo OAuth2 (Authorization Code)

Para integraciones de terceros que necesitan actuar en nombre de un usuario, usa el flujo OAuth2 Authorization Code:

text
Flujo OAuth2 Authorization Code

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Usuario  β”‚                              β”‚   Tu App     β”‚                        β”‚  API     β”‚
β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜                              β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜                        β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
      β”‚  1. Click "Conectar"                      β”‚                                     β”‚
      │──────────────────────────────────────────>β”‚                                     β”‚
      β”‚                                           β”‚  2. Redirect a /oauth/authorize       β”‚
      β”‚<──────────────────────────────────────────│                                     β”‚
      β”‚  3. Login + Autoriza permisos             β”‚                                     β”‚
      │──────────────────────────────────────────>β”‚                                     β”‚
      β”‚                                           β”‚  4. Callback con ?code=abc123         β”‚
      β”‚                                           β”‚<────────────────────────────────────│
      β”‚                                           β”‚  5. POST /oauth/token {code}          β”‚
      β”‚                                           │────────────────────────────────────>β”‚
      β”‚                                           β”‚  6. {access_token, refresh_token}     β”‚
      β”‚                                           β”‚<────────────────────────────────────│
      β”‚  7. Acceso a recursos protegidos          β”‚                                     β”‚
      β”‚<──────────────────────────────────────────│                                     β”‚
bash
# Paso 5: Intercambiar el cΓ³digo por tokens
curl -X POST https://api.cookbooks.dev/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=abc123" \
  -d "client_id=tu_client_id" \
  -d "client_secret=tu_client_secret" \
  -d "redirect_uri=https://tuapp.com/callback"
json
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "rt_dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...",
  "scope": "projects:read projects:write"
}

Refresh de tokens

Cuando un access token expira, usa el refresh token para obtener uno nuevo sin pedir credenciales al usuario:

bash
curl -X POST https://api.cookbooks.dev/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=rt_dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4..." \
  -d "client_id=tu_client_id" \
  -d "client_secret=tu_client_secret"
python
import requests
import time

class TokenManager:
    """Gestiona tokens con refresh automatico."""

    def __init__(self, client_id, client_secret, refresh_token):
        self.client_id = client_id
        self.client_secret = client_secret
        self.refresh_token = refresh_token
        self.access_token = None
        self.expires_at = 0

    def get_token(self):
        if time.time() >= self.expires_at - 60:  # Renovar 60s antes
            self._refresh()
        return self.access_token

    def _refresh(self):
        resp = requests.post("https://api.cookbooks.dev/oauth/token", data={
            "grant_type": "refresh_token",
            "refresh_token": self.refresh_token,
            "client_id": self.client_id,
            "client_secret": self.client_secret,
        })
        resp.raise_for_status()
        data = resp.json()
        self.access_token = data["access_token"]
        self.expires_at = time.time() + data["expires_in"]
        if "refresh_token" in data:
            self.refresh_token = data["refresh_token"]

# Uso
token_mgr = TokenManager("id", "secret", "rt_...")
headers = {"Authorization": f"Bearer {token_mgr.get_token()}"}

Comparacion de esquemas de autenticacion

EsquemaUso recomendadoExpiracionRevocable
API KeyScripts, CI/CD, servidor a servidorNo expira (rotacion manual)Si β€” panel de control
Bearer Token (JWT)Sesiones de usuario, SPAs30 min - 1 horaSi β€” via refresh revocation
OAuth2 CodeIntegraciones de terceros1 hora (refresh: 30 dias)Si β€” revocar autorizacion
OAuth2 Client CredentialsComunicacion machine-to-machine1 horaSi β€” revocar client

Gestion de API Keys

Puedes crear y administrar tus API Keys desde el panel de control o via la propia API:

bash
# Crear una nueva API Key con scope limitado
curl -X POST https://api.cookbooks.dev/v1/api-keys \
  -H "Authorization: Bearer sk_test_51Mz..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "CI/CD Pipeline",
    "scopes": ["projects:read", "deployments:write"],
    "expires_at": "2026-12-31T23:59:59Z"
  }'
json
{
  "id": "key_8fKz3mNpQr",
  "name": "CI/CD Pipeline",
  "key": "sk_live_nuevaClave4821...",
  "scopes": ["projects:read", "deployments:write"],
  "expires_at": "2026-12-31T23:59:59Z",
  "created_at": "2026-02-22T10:00:00Z"
}

Seguridad de las claves

La clave completa solo se muestra una vez al crearla. Guardala de forma segura en un gestor de secretos (como Vault, AWS Secrets Manager o variables de entorno cifradas). Si sospechas que una clave ha sido comprometida, rotala inmediatamente desde el panel de control. Las claves antiguas expiraran tras 24 horas de gracia.

Endpoints & CRUD Core

La API sigue convenciones REST estandar. Todos los endpoints estan bajo el prefijo /v1 y usan los verbos HTTP apropiados para cada operacion.

Referencia de recursos

MetodoEndpointDescripcionStatus
GET/v1/projectsListar proyectos con paginacion200
POST/v1/projectsCrear un nuevo proyecto201
GET/v1/projects/{id}Obtener detalle de un proyecto200
PATCH/v1/projects/{id}Actualizar campos de un proyecto200
DELETE/v1/projects/{id}Eliminar un proyecto204
GET/v1/deploymentsHistorial de builds y estados200
POST/v1/deploymentsIniciar un nuevo despliegue201
GET/v1/teamsListar equipos y miembros200
PUT/v1/teams/{id}Actualizar un equipo completo200
DELETE/v1/teams/{id}Eliminar un equipo204
GET/v1/webhooksListar webhooks configurados200
POST/v1/webhooksRegistrar un nuevo webhook201
DELETE/v1/webhooks/{id}Eliminar un webhook204

GET β€” Listar recursos

bash
# Listar proyectos con filtros y paginacion
curl https://api.cookbooks.dev/v1/projects \
  -H "Authorization: Bearer sk_test_51Mz..." \
  -G \
  -d "status=active" \
  -d "sort=-created_at" \
  -d "per_page=10" \
  -d "page=1"
json
{
  "data": [
    {
      "id": "proj_a1b2c3",
      "name": "docs-website",
      "status": "active",
      "framework": "astro",
      "created_at": "2026-01-15T08:30:00Z",
      "updated_at": "2026-02-20T14:22:00Z",
      "owner": {
        "id": "usr_x9y8z7",
        "email": "dev@cookbooks.dev"
      }
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 10,
    "total": 42,
    "total_pages": 5
  }
}

POST β€” Crear un recurso

bash
# Crear un nuevo proyecto
curl -X POST https://api.cookbooks.dev/v1/projects \
  -H "Authorization: Bearer sk_test_51Mz..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "mi-nuevo-proyecto",
    "framework": "astro",
    "description": "DocumentaciΓ³n tΓ©cnica del equipo",
    "visibility": "private",
    "settings": {
      "build_command": "npm run build",
      "output_dir": "dist",
      "node_version": "20"
    }
  }'
json
{
  "id": "proj_d4e5f6",
  "name": "mi-nuevo-proyecto",
  "framework": "astro",
  "description": "DocumentaciΓ³n tΓ©cnica del equipo",
  "visibility": "private",
  "status": "initializing",
  "settings": {
    "build_command": "npm run build",
    "output_dir": "dist",
    "node_version": "20"
  },
  "created_at": "2026-02-22T10:00:00Z",
  "updated_at": "2026-02-22T10:00:00Z"
}

PATCH β€” Actualizar parcialmente

bash
# Actualizar solo el nombre y la descripcion
curl -X PATCH https://api.cookbooks.dev/v1/projects/proj_d4e5f6 \
  -H "Authorization: Bearer sk_test_51Mz..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "mi-proyecto-renombrado",
    "description": "DescripciΓ³n actualizada"
  }'

PATCH vs PUT

Usa PATCH para enviar solo los campos que cambian. Con PUT debes enviar el recurso completo. La mayoria de nuestros endpoints aceptan PATCH para actualizaciones parciales, que es lo mas comun en integraciones.

DELETE β€” Eliminar un recurso

bash
# Eliminar un proyecto (devuelve 204 sin body)
curl -X DELETE https://api.cookbooks.dev/v1/projects/proj_d4e5f6 \
  -H "Authorization: Bearer sk_test_51Mz..."

Eliminacion irreversible

Las operaciones DELETE son permanentes. Para recursos criticos como proyectos con deployments activos, la API requiere confirmacion via el header X-Confirm-Delete: true.

Query parameters para filtrado y ordenamiento

ParametroTipoEjemploDescripcion
pageinteger?page=2Numero de pagina (desde 1)
per_pageinteger?per_page=25Elementos por pagina (max 100)
sortstring?sort=-created_atOrdenar por campo. Prefijo - para descendente
statusstring?status=activeFiltrar por estado
searchstring?search=docsBusqueda full-text en nombre y descripcion
created_afterISO 8601?created_after=2026-01-01Filtrar por fecha de creacion
fieldsstring?fields=id,name,statusSeleccionar campos especificos (sparse fieldsets)

Request & Response Format Standard

Todas las respuestas de la API siguen un formato envolvente (envelope) consistente. Esto facilita el parseo y la gestion de errores desde cualquier lenguaje.

Formato JSON envolvente

Para respuestas de listado (colecciones):

json
{
  "data": [
    { "id": "proj_a1b2c3", "name": "docs-website", "status": "active" },
    { "id": "proj_d4e5f6", "name": "api-service", "status": "deploying" }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 42,
    "total_pages": 2
  },
  "links": {
    "self": "/v1/projects?page=1&per_page=25",
    "next": "/v1/projects?page=2&per_page=25",
    "last": "/v1/projects?page=2&per_page=25"
  }
}

Para respuestas de recurso individual:

json
{
  "data": {
    "id": "proj_a1b2c3",
    "name": "docs-website",
    "status": "active",
    "framework": "astro",
    "created_at": "2026-01-15T08:30:00Z",
    "updated_at": "2026-02-20T14:22:00Z"
  }
}

Para respuestas de error (ver seccion Error Handling para detalle completo):

json
{
  "error": {
    "code": "validation_error",
    "message": "El campo 'name' es obligatorio",
    "status": 422,
    "details": [
      { "field": "name", "message": "Este campo no puede estar vacΓ­o" }
    ],
    "request_id": "req_7f8g9h0i"
  }
}

Content Negotiation

La API acepta y devuelve JSON por defecto. Debes incluir los headers apropiados:

HeaderValorDescripcion
Content-Typeapplication/jsonFormato del body en requests POST/PATCH/PUT
Acceptapplication/jsonFormato deseado de respuesta (defecto si se omite)
Accept-Encodinggzip, deflateHabilitar compresion en la respuesta
AuthorizationBearer {token}Token de autenticacion
X-Request-Idreq_custom123ID de correlacion personalizado (opcional)
X-Idempotency-Keyidem_abc456Clave de idempotencia para POST (opcional)

Compresion Gzip

La API soporta compresion gzip para reducir el tamano de las respuestas. Las respuestas comprimidas pueden ser hasta un 80% mas pequenas:

bash
# Peticion con compresion gzip
curl https://api.cookbooks.dev/v1/projects \
  -H "Authorization: Bearer sk_test_51Mz..." \
  -H "Accept-Encoding: gzip" \
  --compressed
python
import requests

# requests descomprime gzip automaticamente
response = requests.get(
    "https://api.cookbooks.dev/v1/projects",
    headers={
        "Authorization": "Bearer sk_test_51Mz...",
        "Accept-Encoding": "gzip",
    },
)
# response.json() ya esta descomprimido
print(f"TamaΓ±o recibido: {len(response.content)} bytes")

Headers de respuesta estandar

Cada respuesta incluye headers utiles para debugging y paginacion:

HeaderEjemploDescripcion
X-Request-Idreq_7f8g9h0iID unico de la peticion para soporte
X-RateLimit-Limit1000Peticiones permitidas en la ventana
X-RateLimit-Remaining994Peticiones restantes
X-RateLimit-Reset1708617600Timestamp Unix cuando se reinicia el limite
X-Total-Count42Total de elementos (en listados)
Link</v1/projects?page=2>; rel=β€œnext”Links de navegacion (en listados)

X-Request-Id para soporte

Siempre guarda el header X-Request-Id en tus logs. Cuando contactes a soporte, proporciona este ID para que podamos rastrear tu peticion en nuestros sistemas de forma inmediata.

Error Handling Critical

La API usa codigos de estado HTTP estandar. Todo codigo en el rango 2xx indica exito, 4xx indica un error del cliente, y 5xx indica un error del servidor.

Referencia completa de codigos HTTP

CodigoNombreDescripcionAccion recomendada
200OKPeticion exitosaProcesar la respuesta normalmente
201CreatedRecurso creado exitosamenteLeer el recurso creado en el body
204No ContentOperacion exitosa, sin bodyNo esperar body (ej. DELETE)
400Bad RequestJSON malformado o parametros invalidosRevisar el formato del request
401UnauthorizedToken ausente, invalido o expiradoVerificar credenciales o renovar token
403ForbiddenSin permisos para este recursoVerificar scopes del token y permisos del equipo
404Not FoundRecurso no encontradoVerificar ID del recurso y la URL
409ConflictConflicto de estado (ej. duplicado)Resolver el conflicto antes de reintentar
422Unprocessable EntityValidacion fallida en los datos enviadosRevisar los campos indicados en details
429Too Many RequestsLimite de rate limit excedidoEsperar segun el header Retry-After
500Internal Server ErrorError inesperado del servidorReintentar con backoff exponencial
503Service UnavailableServicio temporalmente no disponibleReintentar tras Retry-After segundos

Formato de error estandar

Todos los errores siguen la misma estructura JSON:

json
{
  "error": {
    "code": "validation_error",
    "message": "Los datos enviados contienen errores de validaciΓ³n",
    "status": 422,
    "request_id": "req_7f8g9h0i",
    "details": [
      {
        "field": "email",
        "message": "Formato de email invΓ‘lido",
        "value": "no-es-un-email"
      },
      {
        "field": "name",
        "message": "Debe tener entre 3 y 100 caracteres",
        "value": "ab"
      }
    ]
  }
}

Estrategia de reintentos con backoff exponencial

Para errores transitorios (429, 500, 503), implementa reintentos con backoff exponencial y jitter:

python
import time
import random
import requests

def request_with_retry(method, url, max_retries=5, **kwargs):
    """
    Realiza una peticion HTTP con reintentos automaticos
    usando backoff exponencial + jitter.
    """
    retryable_codes = {429, 500, 502, 503, 504}

    for attempt in range(max_retries):
        response = requests.request(method, url, **kwargs)

        if response.status_code not in retryable_codes:
            return response

        # Respetar Retry-After si viene en la respuesta
        retry_after = response.headers.get("Retry-After")
        if retry_after:
            wait_time = int(retry_after)
        else:
            # Backoff exponencial: 1s, 2s, 4s, 8s, 16s + jitter
            wait_time = min(2 ** attempt + random.uniform(0, 1), 30)

        print(f"[Retry {attempt + 1}/{max_retries}] "
              f"Status {response.status_code}. "
              f"Esperando {wait_time:.1f}s...")
        time.sleep(wait_time)

    # Retornar la ultima respuesta si se agotan los reintentos
    return response

# Uso
response = request_with_retry(
    "GET",
    "https://api.cookbooks.dev/v1/projects",
    headers={"Authorization": "Bearer sk_test_51Mz..."},
)
javascript
// Implementacion en JavaScript/Node.js
async function fetchWithRetry(url, options = {}, maxRetries = 5) {
  const retryableCodes = new Set([429, 500, 502, 503, 504]);

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (!retryableCodes.has(response.status)) {
      return response;
    }

    const retryAfter = response.headers.get('Retry-After');
    const waitTime = retryAfter
      ? parseInt(retryAfter, 10) * 1000
      : Math.min(2 ** attempt * 1000 + Math.random() * 1000, 30000);

    console.log(
      `[Retry ${attempt + 1}/${maxRetries}] ` +
      `Status ${response.status}. Esperando ${waitTime}ms...`
    );
    await new Promise(resolve => setTimeout(resolve, waitTime));
  }

  throw new Error(`Reintentos agotados tras ${maxRetries} intentos`);
}

// Uso
const response = await fetchWithRetry(
  'https://api.cookbooks.dev/v1/projects',
  { headers: { Authorization: 'Bearer sk_test_51Mz...' } }
);

Idempotencia en reintentos

Solo reintenta peticiones que sean seguras de repetir. Los GET siempre son seguros. Para POST, usa el header X-Idempotency-Key para evitar crear recursos duplicados al reintentar.

Pagination & Filtering

La API soporta dos modos de paginacion: offset-based (por defecto) y cursor-based (recomendado para datasets grandes). Ambos modos estan disponibles en todos los endpoints de listado.

Comparacion de estrategias de paginacion

CaracteristicaOffset (page/per_page)Cursor (after/before)
Salta a pagina arbitrariaSiNo
Rendimiento con datos grandesDegradado (OFFSET lento)Constante (O(1))
Datos consistentes si hay insercionesNo (puede saltar/duplicar)Si
Total de elementosIncluido en metaRequiere has_more flag
Caso de uso idealUIs con numeros de paginaScroll infinito, exports, webhooks

Paginacion offset-based

bash
# Pagina 1, 25 elementos por pagina
curl https://api.cookbooks.dev/v1/projects?page=1&per_page=25 \
  -H "Authorization: Bearer sk_test_51Mz..."

# Pagina 3
curl https://api.cookbooks.dev/v1/projects?page=3&per_page=25 \
  -H "Authorization: Bearer sk_test_51Mz..."

Respuesta con metadatos de paginacion:

json
{
  "data": [ "..." ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 142,
    "total_pages": 6
  },
  "links": {
    "self": "/v1/projects?page=1&per_page=25",
    "first": "/v1/projects?page=1&per_page=25",
    "next": "/v1/projects?page=2&per_page=25",
    "last": "/v1/projects?page=6&per_page=25"
  }
}

Paginacion cursor-based

Para datasets grandes o cuando necesitas consistencia, usa paginacion por cursor:

bash
# Primera peticion (sin cursor)
curl https://api.cookbooks.dev/v1/projects?limit=25&sort=-created_at \
  -H "Authorization: Bearer sk_test_51Mz..."

# Siguiente pagina usando el cursor devuelto
curl https://api.cookbooks.dev/v1/projects?limit=25&after=cur_eyJpZCI6MTAwfQ \
  -H "Authorization: Bearer sk_test_51Mz..."
json
{
  "data": [ "..." ],
  "meta": {
    "has_more": true,
    "next_cursor": "cur_eyJpZCI6NzV9",
    "previous_cursor": "cur_eyJpZCI6MTAwfQ"
  }
}

Iterar todas las paginas con Python

python
import requests

def iter_all_projects(token):
    """Itera todos los proyectos usando paginacion por cursor."""
    url = "https://api.cookbooks.dev/v1/projects"
    params = {"limit": 100, "sort": "-created_at"}
    headers = {"Authorization": f"Bearer {token}"}

    while True:
        resp = requests.get(url, headers=headers, params=params)
        resp.raise_for_status()
        body = resp.json()

        for project in body["data"]:
            yield project

        if not body["meta"].get("has_more"):
            break

        params["after"] = body["meta"]["next_cursor"]

# Uso
for project in iter_all_projects("sk_test_51Mz..."):
    print(f"{project['id']}: {project['name']}")

Los endpoints de listado incluyen el header Link siguiendo el estandar RFC 8288 para facilitar la navegacion:

text
Link: <https://api.cookbooks.dev/v1/projects?page=2&per_page=25>; rel="next",
      <https://api.cookbooks.dev/v1/projects?page=6&per_page=25>; rel="last",
      <https://api.cookbooks.dev/v1/projects?page=1&per_page=25>; rel="first"
javascript
// Parsear el header Link en JavaScript
function parseLinkHeader(header) {
  if (!header) return {};
  return header.split(',').reduce((acc, part) => {
    const match = part.match(/<([^>]+)>;\s*rel="([^"]+)"/);
    if (match) acc[match[2]] = match[1];
    return acc;
  }, {});
}

// Uso
const links = parseLinkHeader(response.headers.get('Link'));
if (links.next) {
  const nextPage = await fetch(links.next, { headers });
}

Filtrado avanzado

Combina multiples filtros en una sola peticion:

bash
# Proyectos activos creados en 2026, framework Astro, ordenados por nombre
curl -G https://api.cookbooks.dev/v1/projects \
  -H "Authorization: Bearer sk_test_51Mz..." \
  -d "status=active" \
  -d "framework=astro" \
  -d "created_after=2026-01-01T00:00:00Z" \
  -d "created_before=2026-12-31T23:59:59Z" \
  -d "sort=name" \
  -d "fields=id,name,status,created_at" \
  -d "per_page=50"

Sparse fieldsets

Usa el parametro fields para recibir solo los campos que necesitas. Esto reduce el tamano de la respuesta y mejora los tiempos de transferencia, especialmente util para listados grandes.

Rate Limiting Important

Limitamos las peticiones para asegurar la estabilidad del sistema y una experiencia justa para todos los usuarios. Usamos un algoritmo de token bucket que permite rafagas cortas por encima del limite promedio.

Niveles de rate limiting

PlanLimite por minutoBurst maximoLimite diarioConcurrencia maxima
Free100 req/min20 req/s10,000 req/dia5 conexiones
Pro1,000 req/min100 req/s100,000 req/dia25 conexiones
Business5,000 req/min500 req/s1,000,000 req/dia100 conexiones
EnterprisePersonalizadoPersonalizadoSin limitePersonalizado

Como funciona el Token Bucket

El algoritmo de token bucket permite rafagas controladas. Imagina un cubo que se llena con tokens a una tasa constante:

text
Algoritmo Token Bucket

  Tokens se agregan a tasa constante (ej. 1000/min)
          β”‚
          β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  β—‹ β—‹ β—‹ β—‹ β—‹ β—‹  β”‚ ← Bucket (capacidad maxima = burst)
  β”‚  β—‹ β—‹ β—‹ β—‹ β—‹ β—‹  β”‚
  β”‚  β—‹ β—‹ β—‹ β—‹ β—‹    β”‚   Cada peticion consume 1 token
  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚
          β–Ό
    Si hay tokens β†’ Peticion permitida (200)
    Si no hay     β†’ Peticion rechazada (429)

Headers de rate limiting

Cada respuesta incluye informacion sobre tu cuota actual:

bash
# Inspeccionar headers de rate limit
curl -I https://api.cookbooks.dev/v1/projects \
  -H "Authorization: Bearer sk_test_51Mz..."
text
HTTP/2 200
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 994
X-RateLimit-Reset: 1708617600
Retry-After: 42

Manejar rate limiting correctamente

Si excedes el limite, recibiras un codigo de estado 429 Too Many Requests. La respuesta incluira el header Retry-After indicando cuantos segundos esperar:

javascript
class APIClient {
  constructor(token) {
    this.token = token;
    this.baseURL = 'https://api.cookbooks.dev/v1';
  }

  async request(method, path, body = null) {
    const url = `${this.baseURL}${path}`;
    const options = {
      method,
      headers: {
        'Authorization': `Bearer ${this.token}`,
        'Content-Type': 'application/json',
      },
    };
    if (body) options.body = JSON.stringify(body);

    const response = await fetch(url, options);

    // Logging de cuota para monitoreo
    const remaining = response.headers.get('X-RateLimit-Remaining');
    const limit = response.headers.get('X-RateLimit-Limit');
    console.log(`Rate limit: ${remaining}/${limit}`);

    // Manejar rate limit
    if (response.status === 429) {
      const retryAfter = parseInt(
        response.headers.get('Retry-After') || '5', 10
      );
      console.warn(`Rate limited. Reintentando en ${retryAfter}s...`);
      await new Promise(r => setTimeout(r, retryAfter * 1000));
      return this.request(method, path, body);  // Reintentar
    }

    return response;
  }
}

Patron Circuit Breaker

Para proteger tu aplicacion cuando la API tiene problemas sostenidos, implementa un circuit breaker:

python
import time

class CircuitBreaker:
    """
    Patron Circuit Breaker para proteger contra fallos en cascada.

    Estados:
    - CLOSED:    Todo funciona, las peticiones pasan normalmente.
    - OPEN:      Demasiados fallos, las peticiones se rechazan inmediatamente.
    - HALF_OPEN: Periodo de prueba, se permite una peticion para verificar.
    """

    CLOSED = "closed"
    OPEN = "open"
    HALF_OPEN = "half_open"

    def __init__(self, failure_threshold=5, recovery_timeout=60):
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.state = self.CLOSED
        self.failure_count = 0
        self.last_failure_time = None

    def can_execute(self):
        if self.state == self.CLOSED:
            return True
        if self.state == self.OPEN:
            if time.time() - self.last_failure_time >= self.recovery_timeout:
                self.state = self.HALF_OPEN
                return True
            return False
        # HALF_OPEN: permitir una peticion de prueba
        return True

    def record_success(self):
        self.failure_count = 0
        self.state = self.CLOSED

    def record_failure(self):
        self.failure_count += 1
        self.last_failure_time = time.time()
        if self.failure_count >= self.failure_threshold:
            self.state = self.OPEN

# Uso con la API
breaker = CircuitBreaker(failure_threshold=5, recovery_timeout=60)

def safe_api_call(url, headers):
    if not breaker.can_execute():
        raise Exception("Circuit breaker OPEN: API no disponible")

    try:
        resp = requests.get(url, headers=headers, timeout=10)
        if resp.status_code >= 500:
            breaker.record_failure()
            raise Exception(f"Server error: {resp.status_code}")
        breaker.record_success()
        return resp
    except requests.exceptions.Timeout:
        breaker.record_failure()
        raise

Monitorea tu cuota

Configura alertas cuando X-RateLimit-Remaining caiga por debajo del 20% de tu limite. Esto te dara tiempo de reaccionar antes de empezar a recibir errores 429.

Webhooks Events

Los webhooks te permiten recibir notificaciones en tiempo real cuando ocurren eventos en tu cuenta. En vez de hacer polling, la API envia un POST a tu URL con los detalles del evento.

Registrar un webhook

bash
curl -X POST https://api.cookbooks.dev/v1/webhooks \
  -H "Authorization: Bearer sk_test_51Mz..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://tuapp.com/api/webhooks/cookbooks",
    "events": [
      "project.created",
      "project.updated",
      "deployment.completed",
      "deployment.failed"
    ],
    "secret": "whsec_tu_secreto_para_verificar_firmas"
  }'
json
{
  "id": "wh_k1l2m3n4",
  "url": "https://tuapp.com/api/webhooks/cookbooks",
  "events": [
    "project.created",
    "project.updated",
    "deployment.completed",
    "deployment.failed"
  ],
  "status": "active",
  "created_at": "2026-02-22T10:00:00Z"
}

Eventos disponibles

EventoDescripcionPayload clave
project.createdNuevo proyecto creadoproject object
project.updatedProyecto modificadoproject object + changes
project.deletedProyecto eliminadoproject id
deployment.startedDespliegue iniciadodeployment object
deployment.completedDespliegue exitosodeployment object + url
deployment.failedDespliegue fallidodeployment object + error
team.member_addedNuevo miembro en el equipoteam id + user object
team.member_removedMiembro eliminado del equipoteam id + user id

Formato del payload

Cada webhook envia un POST con el siguiente formato JSON:

json
{
  "id": "evt_p5q6r7s8",
  "type": "deployment.completed",
  "created_at": "2026-02-22T10:05:00Z",
  "data": {
    "id": "dpl_t9u0v1w2",
    "project_id": "proj_a1b2c3",
    "status": "success",
    "url": "https://docs-website.cookbooks.dev",
    "commit_sha": "a1b2c3d4",
    "duration_seconds": 45
  }
}

Verificacion de firma con HMAC-SHA256

Cada peticion de webhook incluye el header X-Cookbooks-Signature que debes verificar para asegurar que la peticion proviene de nuestra API y no ha sido alterada:

python
import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = "whsec_tu_secreto_para_verificar_firmas"

def verify_signature(payload_body, signature_header):
    """Verifica la firma HMAC-SHA256 del webhook."""
    expected = hmac.new(
        key=WEBHOOK_SECRET.encode("utf-8"),
        msg=payload_body,
        digestmod=hashlib.sha256,
    ).hexdigest()

    received = signature_header.replace("sha256=", "")
    return hmac.compare_digest(expected, received)

@app.route("/api/webhooks/cookbooks", methods=["POST"])
def handle_webhook():
    signature = request.headers.get("X-Cookbooks-Signature", "")

    if not verify_signature(request.data, signature):
        abort(401, "Firma invalida")

    event = request.json
    event_type = event["type"]

    # Procesar segun el tipo de evento
    if event_type == "deployment.completed":
        handle_deployment_completed(event["data"])
    elif event_type == "deployment.failed":
        handle_deployment_failed(event["data"])
    elif event_type == "project.created":
        handle_project_created(event["data"])

    # Responder 200 rapidamente para confirmar recepcion
    return {"received": True}, 200
javascript
// Verificacion en Node.js (Express)
const crypto = require('crypto');
const express = require('express');
const app = express();

const WEBHOOK_SECRET = 'whsec_tu_secreto_para_verificar_firmas';

app.post('/api/webhooks/cookbooks',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['x-cookbooks-signature'] || '';
    const expected = 'sha256=' + crypto
      .createHmac('sha256', WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex');

    if (!crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expected)
    )) {
      return res.status(401).json({ error: 'Firma invalida' });
    }

    const event = JSON.parse(req.body);
    console.log(`Evento recibido: ${event.type}`);

    // Procesar de forma asincrona (no bloquear la respuesta)
    processEvent(event).catch(console.error);

    res.json({ received: true });
  }
);

Politica de reintentos de webhooks

Si tu endpoint no responde con un codigo 2xx dentro de 10 segundos, reintentaremos la entrega con el siguiente calendario:

IntentoEsperaTiempo acumulado
1 (original)Inmediato0 min
21 minuto1 min
35 minutos6 min
430 minutos36 min
52 horas~2.5 horas
6 (ultimo)8 horas~10.5 horas

Idempotencia en webhooks

Cada evento incluye un id unico. Dado que un mismo evento puede entregarse mas de una vez (por reintentos), tu handler debe ser idempotente:

python
# Ejemplo: usar Redis para deduplicar eventos
import redis

r = redis.Redis()

def handle_webhook(event):
    event_id = event["id"]

    # Verificar si ya procesamos este evento
    if r.get(f"webhook:processed:{event_id}"):
        print(f"Evento {event_id} ya procesado, ignorando duplicado")
        return {"received": True}

    # Procesar el evento
    process_event(event)

    # Marcar como procesado (TTL de 7 dias)
    r.set(f"webhook:processed:{event_id}", "1", ex=604800)

    return {"received": True}

Responde rapido, procesa despues

Tu endpoint de webhook debe responder con 200 lo mas rapido posible (idealmente en menos de 5 segundos). Si necesitas procesamiento pesado, encola el evento y procesalo en un worker en segundo plano.

SDK & Client Generation Tools

Nuestra API publica una especificacion OpenAPI 3.1 completa que puedes usar para generar clientes automaticamente en cualquier lenguaje. Tambien proporcionamos SDKs oficiales para Python y TypeScript.

Especificacion OpenAPI

La especificacion esta disponible en formato JSON y YAML:

bash
# Descargar la especificacion OpenAPI
curl https://api.cookbooks.dev/v1/openapi.json -o openapi.json

# O en formato YAML
curl https://api.cookbooks.dev/v1/openapi.yaml -o openapi.yaml

# Validar la especificacion localmente
npx @redocly/cli lint openapi.json

SDK oficial de Python

bash
pip install cookbooks-sdk
python
from cookbooks import CookbooksClient

# Inicializar el cliente
client = CookbooksClient(
    api_key="sk_test_51Mz...",
    base_url="https://api.cookbooks.dev/v1",  # Opcional
    timeout=30,     # Timeout en segundos
    max_retries=3,  # Reintentos automaticos
)

# --- Proyectos ---

# Listar proyectos con filtros
projects = client.projects.list(
    status="active",
    sort="-created_at",
    per_page=50,
)
for project in projects:
    print(f"{project.id}: {project.name}")

# Crear un proyecto
new_project = client.projects.create(
    name="api-docs",
    framework="astro",
    visibility="private",
    settings={
        "build_command": "npm run build",
        "output_dir": "dist",
    },
)
print(f"Proyecto creado: {new_project.id}")

# Actualizar un proyecto
updated = client.projects.update(
    "proj_a1b2c3",
    name="api-docs-v2",
    description="Documentacion actualizada",
)

# Eliminar un proyecto
client.projects.delete("proj_a1b2c3")

# --- Deployments ---

# Listar deployments de un proyecto
deployments = client.deployments.list(project_id="proj_a1b2c3")

# Iniciar un nuevo deployment
deploy = client.deployments.create(
    project_id="proj_a1b2c3",
    branch="main",
)
print(f"Deployment: {deploy.id} β€” Status: {deploy.status}")

# --- Manejo de errores ---
from cookbooks.exceptions import (
    NotFoundError,
    RateLimitError,
    ValidationError,
)

try:
    project = client.projects.get("proj_inexistente")
except NotFoundError:
    print("Proyecto no encontrado")
except RateLimitError as e:
    print(f"Rate limited. Reintentar en {e.retry_after}s")
except ValidationError as e:
    print(f"Error de validacion: {e.details}")

SDK oficial de TypeScript/JavaScript

bash
npm install @cookbooks/sdk
javascript
import { CookbooksClient } from '@cookbooks/sdk';

// Inicializar el cliente
const client = new CookbooksClient({
  apiKey: 'sk_test_51Mz...',
  timeout: 30000,   // Timeout en ms
  maxRetries: 3,    // Reintentos automaticos
});

// Listar proyectos
const { data: projects, meta } = await client.projects.list({
  status: 'active',
  sort: '-created_at',
  perPage: 50,
});

console.log(`Total: ${meta.total} proyectos`);
projects.forEach(p => console.log(`${p.id}: ${p.name}`));

// Crear un proyecto
const newProject = await client.projects.create({
  name: 'api-docs',
  framework: 'astro',
  visibility: 'private',
  settings: {
    buildCommand: 'npm run build',
    outputDir: 'dist',
  },
});

// Actualizar un proyecto
const updated = await client.projects.update('proj_a1b2c3', {
  name: 'api-docs-v2',
});

// Eliminar un proyecto
await client.projects.delete('proj_a1b2c3');

// Manejo de errores con tipos
import {
  NotFoundError,
  RateLimitError,
  ValidationError,
} from '@cookbooks/sdk';

try {
  const project = await client.projects.get('proj_inexistente');
} catch (error) {
  if (error instanceof NotFoundError) {
    console.log('Proyecto no encontrado');
  } else if (error instanceof RateLimitError) {
    console.log(`Reintentar en ${error.retryAfter}s`);
  } else if (error instanceof ValidationError) {
    console.log('Errores:', error.details);
  }
}

Generar clientes con OpenAPI Generator

Si prefieres otro lenguaje, genera un cliente automaticamente desde la especificacion OpenAPI:

bash
# Instalar OpenAPI Generator
npm install -g @openapitools/openapi-generator-cli

# Generar cliente en Go
openapi-generator-cli generate \
  -i https://api.cookbooks.dev/v1/openapi.json \
  -g go \
  -o ./cookbooks-go-client \
  --additional-properties=packageName=cookbooks

# Generar cliente en Java
openapi-generator-cli generate \
  -i https://api.cookbooks.dev/v1/openapi.json \
  -g java \
  -o ./cookbooks-java-client \
  --additional-properties=artifactId=cookbooks-client

# Generar cliente en Rust
openapi-generator-cli generate \
  -i https://api.cookbooks.dev/v1/openapi.json \
  -g rust \
  -o ./cookbooks-rust-client

Coleccion de Postman

Importa nuestra coleccion de Postman para explorar y probar la API de forma interactiva:

bash
# Descargar la coleccion de Postman
curl https://api.cookbooks.dev/v1/postman-collection.json \
  -o cookbooks-postman-collection.json

Tambien puedes importarla directamente en Postman usando esta URL:

text
https://api.cookbooks.dev/v1/postman-collection.json

La coleccion incluye:

  • Todos los endpoints organizados por recurso
  • Variables de entorno preconfiguradas (base_url, api_key)
  • Ejemplos de request y response para cada operacion
  • Scripts de pre-request para autenticacion automatica
  • Tests automaticos para validar respuestas

Documentacion interactiva

Ademas de Postman, puedes explorar la API directamente en tu navegador visitando https://api.cookbooks.dev/docs (Swagger UI) o https://api.cookbooks.dev/redoc (ReDoc). Ambas interfaces se generan automaticamente desde la especificacion OpenAPI.

END OF DOCUMENT

ΒΏNecesitas mΓ‘s? Volver a la LibrerΓ­a →