Skip to main content

Documentation Index

Fetch the complete documentation index at: https://ryvo-3dab4d1a.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Endpoints POST que crean recursos (como /v1/calls) aceptan un header Idempotency-Key. Si tu sistema reintenta una petición por timeout o error de red, mandar la misma key garantiza que la operación se ejecute una sola vez.

Cómo funciona

POST /v1/calls
Authorization: Bearer ryvo_live_...
Idempotency-Key: lead-abc-2026-04-30-001
Content-Type: application/json

{ "agent_id": "agent_8801kpabc123", "to": "+5215555550100" }
Cuando recibimos esta petición:
  1. Buscamos en cache por la combinación (client_id, endpoint, key).
  2. Hit con mismo body → devolvemos la respuesta cacheada de la primera petición. No volvemos a disparar la llamada.
  3. Hit con body distinto → respondemos 409 idempotency_conflict. Es un bug en tu lado: estás reusando una key con datos diferentes.
  4. Miss → procesamos la petición normal. Si es exitosa, guardamos (key, body, response) en cache por 24h.

Cuándo usarla

Disparar llamadas desde un workflow

Tu n8n o GHL puede reintentar por timeout. Sin idempotency, cada reintento dispara una llamada nueva.

Mensajes en cola

Si tu cola entrega mensajes “at-least-once” (SQS, RabbitMQ), el mismo mensaje puede llegar 2 veces.

Webhooks de tu CRM

Tu CRM puede reintentar webhooks fallidos — usa el mismo ID del CRM como Idempotency-Key.

Cualquier código async

Donde reintenten reintentos, manda la key.

Formato de la key

  • 1 a 255 caracteres.
  • Solo ASCII imprimible (! a ~).
  • Recomendado: UUID v4 (crypto.randomUUID()) o un identificador de tu sistema (ej. lead-{lead_id}-{deal_id}).

TTL del cache

Las keys expiran 24 horas después de la primera petición exitosa. Pasado ese plazo, la misma key puede usarse para una nueva operación distinta. Si necesitas garantía de duplicados más allá de 24h, usa identificadores estables del lado de tu sistema (ej. webhook_id único en tu CRM) y guarda tu propio mapping webhook_id → call_id.

Aislamiento por cliente

Las keys están aisladas por API key (cliente). Si tú usas Idempotency-Key: 12345 y otro cliente Ryvo usa la misma cadena, no hay colisión — son namespaces separados.

Ejemplo: integración con backoff

const idempotencyKey = `lead-${lead.id}-${deal.id}`

async function dispararLlamada(retries = 3) {
  for (let attempt = 0; attempt <= retries; attempt++) {
    try {
      const r = await fetch("https://api.ryvo.so/v1/calls", {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${process.env.RYVO_API_KEY}`,
          "Content-Type": "application/json",
          "Idempotency-Key": idempotencyKey,
        },
        body: JSON.stringify({ agent_id: agent.id, to: lead.phone }),
      })

      if (r.ok) return await r.json()

      // 4xx (excepto 429) → no reintentes
      if (r.status >= 400 && r.status < 500 && r.status !== 429) {
        throw new Error(`API error: ${(await r.json()).error}`)
      }

      // 429 / 5xx → backoff
      const delay = Math.pow(2, attempt) * 1000 + Math.random() * 500
      await new Promise(r => setTimeout(r, delay))
    } catch (err) {
      if (attempt === retries) throw err
    }
  }
}
Como mandamos siempre la misma idempotencyKey, aunque el primer intento haya disparado la llamada exitosa pero el response se perdió en la red, el reintento devuelve la misma respuesta cacheada y NO dispara una segunda llamada.

Errores comunes

ErrorCausa
400 invalid_idempotency_keyCaracteres no-ASCII o más de 255 chars.
409 idempotency_conflictReusaste una key con un body distinto. Cambia uno o el otro.