Referencia de API

Este documento coincide con el comportamiento de la aplicación Express en api/app.js y los manejadores de rutas bajo api/routes/.

Límites y comportamiento

ÍtemValor
Tamaño del cuerpo JSONHasta 2 MB (express.json({ limit: '2mb' }))
Objetivos por solicitud1–36 códigos de idioma
Ítems por lote1–100 ítems por solicitud por lote
Modelosstandard (predeterminado) o advanced (solo niveles de pago; ver abajo)

Asignación mensual de tokens (nivel gratuito): Antes de llamar al modelo, la API estima los tokens aproximadamente como ceil(content_length / 4) × (number_of_targets + 1) y, solo para el nivel free, rechaza la solicitud con 429 / token_limit_reached si la estimación excede la asignación mensual restante (FREE_TIER_MONTHLY_TOKENS, predeterminado 100000). Los niveles de pago no son bloqueados por esta pre-verificación en enforceTokenCap; el uso aún se registra.

Límites de tasa: Cuando Upstash Redis está configurado (UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN, y la URL no contiene el marcador your-instance), se aplican límites por minuto según el nivel: free 5, starter 30, growth 60, scale 120, enterprise ilimitado. Al alcanzar el límite, la respuesta es 429 con error: "rate_limit_reached". Si Redis no está configurado, se omite el límite de tasa (ver rateLimit.js).

Las respuestas exitosas con límite de tasa pueden incluir X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.


GET /health

Sin autenticación.

Respuesta 200

{
  "status": "ok",
  "timestamp": "2025-03-23T12:00:00.000Z"
}

GET /languages

Sin autenticación.

Devuelve la lista canónica de idiomas soportados (código, nombre para mostrar, bandera RTL). Hay 36 entradas; los códigos son los únicos valores aceptados en targets en los endpoints de traducción.

Respuesta 200

{
  "languages": [
    { "code": "en", "name": "English", "rtl": false },
    { "code": "ar", "name": "Arabic", "rtl": true }
  ]
}

Fuente: api/utils/languages.js.


POST /translate

Requiere Authorization: Bearer <api_key>.

Traduce una única cadena content a cada idioma listado en targets. El modelo devuelve un único objeto JSON cuyas claves son exactamente los códigos de idioma solicitados y cuyos valores son cadenas traducidas (ver formatPrompts.js).

Cuerpo de la solicitud

CampoTipoRequeridoDescripción
contentstringCadena no vacía para traducir.
targetsstring[]Array no vacío de códigos de idioma válidos (máx. 36).
formatstringNoUno de plain, markdown, json, html. Si se omite, el formato se detecta automáticamente desde content.
sourcestringNoPista de idioma fuente para el modelo; opcional.
modelstringNostandard (predeterminado) o advanced. advanced requiere nivel de pago (403 en free).

Respuesta 200

{
  "translations": {
    "es": "...",
    "fr": "..."
  },
  "usage": {
    "input_tokens": 120,
    "output_tokens": 340,
    "total_tokens": 460,
    "model": "standard",
    "detected_format": "markdown",
    "detection_confidence": 0.95
  }
}

detected_format y detection_confidence aparecen solo cuando format fue omitido y se ejecutó la detección automática.

Ejemplo (cURL)

curl -sS -X POST "https://api.usepolylingo.com/v1/translate" \
  -H "Authorization: Bearer $POLYLINGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "{\"title\":\"Hello\"}",
    "format": "json",
    "targets": ["fr", "de"]
  }'

Ejemplo (Python 3)

pip install requests
import os, requests

url = "https://api.usepolylingo.com/v1/translate"
headers = {
    "Authorization": f"Bearer {os.environ['POLYLINGO_API_KEY']}",
    "Content-Type": "application/json",
}
r = requests.post(url, json={
    "content": "<p>Hello <strong>world</strong></p>",
    "format": "html",
    "targets": ["es"],
}, timeout=120)
r.raise_for_status()
print(r.json()["translations"]["es"])

POST /translate/batch

Requiere Authorization: Bearer <api_key>.

Procesa cada ítem secuencialmente (una llamada al modelo por ítem). Si algún ítem falla, la API devuelve 500 y no devuelve resultados parciales para esa solicitud.

Cuerpo de la solicitud

CampoTipoRequeridoDescripción
itemsarrayCada elemento: id (string), content (string), format opcional.
targetsstring[]Mismas reglas que /translate.
sourcestringNoPista de idioma fuente opcional.
modelstringNostandard o advanced (mismas reglas que /translate).

Respuesta 200

{
  "results": [
    { "id": "welcome", "translations": { "fr": "...", "de": "..." } },
    { "id": "goodbye", "translations": { "fr": "...", "de": "..." } }
  ],
  "usage": {
    "total_tokens": 900,
    "input_tokens": 400,
    "output_tokens": 500,
    "model": "standard"
  }
}

Ejemplo

curl -sS -X POST "https://api.usepolylingo.com/v1/translate/batch" \
  -H "Authorization: Bearer $POLYLINGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "id": "a", "content": "Hello", "format": "plain" },
      { "id": "b", "content": "## Title", "format": "markdown" }
    ],
    "targets": ["es", "it"]
  }'

POST /jobs

Requiere Authorization: Bearer <api_key>.

Encola un trabajo de traducción y devuelve inmediatamente un job_id. La traducción se ejecuta en segundo plano — sin riesgo de timeout HTTP sin importar el tamaño del contenido. Consulta GET /jobs/:id para el resultado.

Usa este endpoint en lugar de POST /translate cuando traduzcas documentos grandes (Markdown largo, muchos idiomas destino) donde la duración de la solicitud podría exceder el timeout de tu cliente HTTP o proxy.

Cuerpo de la solicitud

CampoTipoRequeridoDescripción
contentstringCadena no vacía para traducir.
targetsstring[]Array no vacío de códigos de idioma válidos (máx. 36).
formatstringNoUno de plain, markdown, json, html. Detectado automáticamente si se omite.
sourcestringNoPista de idioma fuente; opcional.
modelstringNostandard (predeterminado) o advanced.

Respuesta 202

{
  "job_id": "a1b2c3d4-...",
  "status": "pending",
  "created_at": "2025-03-23T12:00:00.000Z"
}

GET /jobs/:id

Requiere Authorization: Bearer <api_key>.

Consulta el estado de un trabajo enviado vía POST /jobs. Consulta cada 5–10 segundos. Los trabajos pertenecen al usuario que los envió — otros usuarios reciben 404.

Respuesta (pendiente / procesando)

{
  "job_id": "a1b2c3d4-...",
  "status": "pending",
  "created_at": "2025-03-23T12:00:00.000Z",
  "updated_at": "2025-03-23T12:00:00.000Z",
  "completed_at": null,
  "queue_position": 3
}

status es pending (esperando un worker) o processing (worker lo ha reclamado). queue_position (base 1) indica cuántos trabajos pendientes o en proceso fueron creados estrictamente antes que este — úsalo para UI de progreso. Se omite si la consulta de conteo falla.

Respuesta (completado)

{
  "job_id": "a1b2c3d4-...",
  "status": "completed",
  "created_at": "2025-03-23T12:00:00.000Z",
  "updated_at": "2025-03-23T12:00:02.000Z",
  "completed_at": "2025-03-23T12:00:02.000Z",
  "translations": {
    "es": "...",
    "fr": "..."
  },
  "usage": {
    "input_tokens": 120,
    "output_tokens": 340,
    "total_tokens": 460,
    "model": "standard"
  }
}

Respuesta (fallido)

{
  "job_id": "a1b2c3d4-...",
  "status": "failed",
  "error": "Model returned invalid JSON"
}

Ejemplo (JavaScript)

const API = 'https://api.usepolylingo.com/v1'
const headers = {
  'Authorization': `Bearer ${process.env.POLYLINGO_API_KEY}`,
  'Content-Type': 'application/json',
}

// 1. Enviar
const submit = await fetch(`${API}/jobs`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ content: longMarkdown, format: 'markdown', targets: ['de', 'fr'] }),
})
const { job_id } = await submit.json()

// 2. Consultar
while (true) {
  await new Promise(r => setTimeout(r, 10_000))
  const poll = await fetch(`${API}/jobs/${job_id}`, { headers })
  const job = await poll.json()
  if (job.status === 'completed') { console.log(job.translations); break }
  if (job.status === 'failed')    { throw new Error(job.error) }
  // Opcional: mostrar progreso (queue_position es base 1, se omite si no está en cola)
  if (job.queue_position != null) console.log(`Queue position: ${job.queue_position}`)
}

GET /usage

Requiere Authorization: Bearer <api_key> (búsqueda estándar de clave — no la ruta interna solo para bypass).

Devuelve el uso de tokens para el mes calendario actual para el usuario autenticado.

Respuesta 200

{
  "period_start": "2025-03-01T00:00:00.000Z",
  "period_end": "2025-03-31T23:59:59.000Z",
  "tokens_used": 12000,
  "tokens_included": 100000,
  "tokens_remaining": 88000,
  "overage_tokens": 0,
  "tier": "free"
}

tokens_included y tokens_remaining son null para enterprise (asignación ilimitada en reportes).


Formatos de contenido

Valores soportados para format: plain, markdown, json, html.

FormatoConservadoTraducido
plainSaltos de línea / párrafosTodo el texto visible
markdownSintaxis, enlaces (URL sin cambios), código en bloque (literal)Prosa y texto de enlaces
jsonClaves, estructura, tipos no cadenaSolo valores de cadena
htmlEtiquetas y atributosNodos de texto y atributos apropiados (ver prompts)

RTL y dirección en tu app

Para salida plain y markdown, la API devuelve solo el texto traducido — no añade dir="rtl" ni elementos contenedores. Establece la dirección del texto en tu UI (CSS direction, atributo dir de un elemento padre o el layout i18n de tu framework) al mostrar árabe, hebreo o persa.

Para formato html, el marcado traducido puede incluir dir="rtl" donde sea apropiado para objetivos RTL; ver formatPrompts.js y las pruebas HTML en scripts/test-translation.js.


Respuestas de error

Los errores son JSON cuando es posible:

{
  "error": "invalid_request",
  "message": "Detalle legible para humanos"
}
HTTPerrorCuándo
400invalid_requestCampos del cuerpo faltantes/incorrectos (ej. content vacío, targets inválidos)
400invalid_formatformat no está en el conjunto soportado
400invalid_languageCódigo desconocido en targets
401invalid_api_keyAuthorization faltante/malformado, clave desconocida, clave revocada
403advanced_not_availablemodel: "advanced" en nivel gratuito
429token_limit_reachedSe excedería el límite mensual del nivel gratuito (pre-verificación)
429rate_limit_reachedLímite RPM por minuto (cuando Redis está habilitado)
500translation_errorFallo del modelo/red; seguro para reintentar
404not_foundGET /jobs/:id — trabajo no existe o pertenece a otro usuario
500server_errorPOST /jobs — fallo al encolar; seguro para reintentar

GET /usage puede devolver 500 con un mensaje genérico si falla la consulta Supabase.


Modelos subyacentes (informativo)

La API expone solo standard y advanced. Los IDs reales de modelos OpenAI se configuran en api/utils/modelRouter.js y no se devuelven en las respuestas API.

Referencia API | PolyLingo | PolyLingo