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
| Ítem | Valor |
|---|---|
| Tamaño del cuerpo JSON | Hasta 2 MB (express.json({ limit: '2mb' })) |
| Objetivos por solicitud | 1–36 códigos de idioma |
| Ítems por lote | 1–100 ítems por solicitud por lote |
| Modelos | standard (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
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
content | string | Sí | Cadena no vacía para traducir. |
targets | string[] | Sí | Array no vacío de códigos de idioma válidos (máx. 36). |
format | string | No | Uno de plain, markdown, json, html. Si se omite, el formato se detecta automáticamente desde content. |
source | string | No | Pista de idioma fuente para el modelo; opcional. |
model | string | No | standard (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
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
items | array | Sí | Cada elemento: id (string), content (string), format opcional. |
targets | string[] | Sí | Mismas reglas que /translate. |
source | string | No | Pista de idioma fuente opcional. |
model | string | No | standard 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
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
content | string | Sí | Cadena no vacía para traducir. |
targets | string[] | Sí | Array no vacío de códigos de idioma válidos (máx. 36). |
format | string | No | Uno de plain, markdown, json, html. Detectado automáticamente si se omite. |
source | string | No | Pista de idioma fuente; opcional. |
model | string | No | standard (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.
| Formato | Conservado | Traducido |
|---|---|---|
plain | Saltos de línea / párrafos | Todo el texto visible |
markdown | Sintaxis, enlaces (URL sin cambios), código en bloque (literal) | Prosa y texto de enlaces |
json | Claves, estructura, tipos no cadena | Solo valores de cadena |
html | Etiquetas y atributos | Nodos 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"
}
| HTTP | error | Cuándo |
|---|---|---|
| 400 | invalid_request | Campos del cuerpo faltantes/incorrectos (ej. content vacío, targets inválidos) |
| 400 | invalid_format | format no está en el conjunto soportado |
| 400 | invalid_language | Código desconocido en targets |
| 401 | invalid_api_key | Authorization faltante/malformado, clave desconocida, clave revocada |
| 403 | advanced_not_available | model: "advanced" en nivel gratuito |
| 429 | token_limit_reached | Se excedería el límite mensual del nivel gratuito (pre-verificación) |
| 429 | rate_limit_reached | Límite RPM por minuto (cuando Redis está habilitado) |
| 500 | translation_error | Fallo del modelo/red; seguro para reintentar |
| 404 | not_found | GET /jobs/:id — trabajo no existe o pertenece a otro usuario |
| 500 | server_error | POST /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.