API de Owl3D Cloud
La API convierte un vídeo 2D a 3D lado a lado. El código obtiene un precio, sube el archivo, espera el trabajo y descarga el resultado. Ejecuta la misma conversión que Owl3D Cloud.
Un agente de programación con IA puede leer esta página como texto plano en /llms.txt.
Antes de empezar
- Iniciar sesión en app.owl3d.com y verificar el correo electrónico.
- Tener un plan o comprar créditos. Un trabajo gasta primero los créditos mensuales del plan y después los créditos comprados. Los créditos de prueba también cuentan.
- Abrir Claves de API y crear una clave. La página muestra la clave una sola vez.
Guardar la clave en la variable de entorno OWL3D_API_KEY. Todos los ejemplos de esta página la leen de ahí.
El uso de la API se rige por los Owl3D Cloud API Terms (en inglés).
export OWL3D_API_KEY="o3d_live_..."Autenticación
| Elemento | Valor |
|---|---|
| URL base | https://api.owl3d.com |
| Formato de la clave | o3d_live_<keyId>_<secret> |
| Encabezado de autenticación | Authorization: Bearer <key> |
Escribir Bearer con B mayúscula y un solo espacio. Otras formas reciben 401.
Cada ruta necesita un scope: jobs:read o jobs:write. La tabla de rutas indica el scope de cada ruta.
La clave de API debe mantenerse en secreto. Puede gastar los créditos. Si una clave se filtra, se revoca en la página de claves de API. Una clave revocada deja de funcionar en 60 segundos.
GET /v1/jobs/{id}
curl https://api.owl3d.com/v1/jobs/job_... \
-H "Authorization: Bearer $OWL3D_API_KEY"{"id": "job_...", "status": "converting"}Convertir un vídeo
Descargar un script y ejecutarlo con el vídeo. Cada script realiza todos los pasos siguientes.
| Script | Requisitos | Ejecución |
|---|---|---|
| convert.sh | bash, curl, jq, ffprobe | bash convert.sh my-video.mp4 |
| convert.py | Python 3.9+, requests, ffprobe |
python3 convert.py my-video.mp4 |
| convert.mjs | Node.js 20+, ffprobe | node convert.mjs my-video.mp4 |
convert.sh · convert.py · convert.mjs
curl -O https://www.owl3d.com/docs/api/convert.sh
bash convert.sh my-video.mp4Los scripts son opcionales. Tu código puede usar cualquier lenguaje. Necesita llamadas HTTPS, SHA-256 y los datos del archivo. Con subir primero, solo necesita llamadas HTTPS.
El flujo tiene seis pasos:
- Leer los datos del archivo y generar la huella.
POST /v1/estimates: obtener el precio. No se cobra nada.POST /v1/jobs: aprobar el precio. Esto reserva los créditos.- Enviar cada parte con
PUTa su URL de subida y despuésPOST /v1/jobs/{id}/upload-complete. - Consultar
GET /v1/jobs/{id}cada 15 segundos. GET /v1/jobs/{id}/result: obtener un enlace de descarga. El enlace funciona 15 minutos y solo paraGET.
Subir primero omite los pasos 1 y 2. Tu código envía el nombre y el tamaño del archivo a POST /v1/jobs. Después de la subida, el servidor lee el archivo y fija el precio.
Errores
La mayoría de las respuestas de error tienen un cuerpo JSON con error, un código corto.
| Estado | Significado |
|---|---|
400 |
Solicitud incorrecta, por ejemplo bad_fingerprint |
401 |
Sin token, un token incorrecto o una clave revocada |
402 |
short_of_credits: los créditos del plan, de prueba y comprados juntos no cubren el precio |
403 |
insufficient_scope, o la ruta no es pública |
404 |
no_job. Los trabajos creados en la app Cloud no son visibles para la API |
409 |
Conflicto de estado, por ejemplo not_uploading o endpoint_exists |
410 |
El resultado expiró, o insufficient_credits_expired: el trabajo esperó créditos durante 24 horas |
429 |
Un límite está lleno. Ver Límites |
Reglas de reintento:
- Con
429, esperarRetry-Aftersegundos y enviar la misma solicitud. - Con
5xxo un error de red, aumentar la espera de forma gradual de 1 segundo a 60 segundos. - Con cualquier otro
4xx, corregir la solicitud.
curl -i https://api.owl3d.com/v1/jobs/job_unknown \
-H "Authorization: Bearer $OWL3D_API_KEY"{"error": "no_job"}Límites
| Límite | Valor | Respuesta |
|---|---|---|
| Trabajos en curso | 10 para todas las claves de API de una cuenta juntas. 15 para toda la cuenta, todos los canales juntos. Límite opcional por clave | 429 concurrency_limit, Retry-After: 15 |
| Solicitudes | 120 por minuto y por clave | 429, Retry-After: 60 |
| Ráfaga | Cada ruta 10 por segundo, ráfaga de 20, para todos los llamadores en conjunto | 429 throttled, Retry-After: 1 |
| Tope diario de créditos | Opcional por clave | 429 daily_credit_cap, hasta las 00:00 UTC |
| Claves activas | 10 por cuenta | Creación rechazada |
| Entrada | 100 GB, 3 horas, 4096 x 2160, 60 fps | Estimación rechazada |
| Contenedores | MP4, MOV, MKV, M4V | Estimación rechazada |
El cuerpo de un 429 concurrency_limit indica el límite que está lleno. scope es api (todas las claves de API de la cuenta), account (todos los trabajos de la cuenta) o key (esta clave). Iniciar el siguiente trabajo cuando termine uno.
{
"error": "concurrency_limit",
"scope": "api",
"limit": 10,
"inProgress": 10
}Precios
POST /v1/estimates da el precio exacto de un archivo antes de pagar.
En los dos casos, el servidor lee el archivo subido y fija el precio final a partir de él. Con subir primero, ves el precio solo después de la subida. Si el saldo no cubre el precio, el trabajo espera. Consulta Créditos insuficientes.
Rutas
| Método y ruta | Scope | Función |
|---|---|---|
POST /v1/estimates |
jobs:write |
Precio y tiempo. No se cobra nada. Válido 1 hora, para un trabajo |
POST /v1/jobs |
jobs:write |
Reserva créditos y devuelve el plan de subida. Con estimateId, o con filename y sizeBytes (subir primero) |
GET /v1/jobs/{id}/upload |
jobs:read |
Estado de la subida: done, missing y URL parts nuevas |
POST /v1/jobs/{id}/upload-complete |
jobs:write |
Envía los ETags y pone el trabajo en cola |
GET /v1/jobs/{id} |
jobs:read |
Estado, progreso, tiempo restante |
POST /v1/jobs/{id}/cancel |
jobs:write |
Cancela el trabajo |
POST /v1/jobs/{id}/resume |
jobs:write |
Continúa un trabajo insufficient_credits después de añadir créditos. Sin subir de nuevo |
GET /v1/jobs/{id}/result |
jobs:read |
Enlace de descarga, 15 minutos |
Estimaciones
POST /v1/estimates da el precio y el tiempo de un archivo. No se cobra nada. Una estimación es válida 1 hora, para un trabajo.
| Campo | Valor |
|---|---|
name, size |
El nombre del archivo y su tamaño en bytes |
durationSeconds, width, height, fps |
Leídos del archivo con cualquier herramienta, como ffprobe o MediaInfo |
codec |
Uno de H.264, HEVC, AV1, VP9 |
bitDepth, chroma |
Leídos del archivo. chroma es 4:2:0 |
fingerprint |
{size, headSha256, tailSha256, edgeBytes} |
edgeByteses 4194304, o el tamaño del archivo si es más pequeño.headSha256ytailSha256son los hashes de los primeros y los últimosedgeBytesdel archivo.
En la respuesta, "ok": false significa que la estimación se rechaza. Leer reason. Si credits.shortBy es mayor que 0, la cuenta necesita más créditos. credits.fromPlan, credits.fromTrial y credits.fromPurchased indican qué créditos pagan el precio.
POST /v1/estimates
curl https://api.owl3d.com/v1/estimates \
-H "Authorization: Bearer $OWL3D_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "my-video.mp4",
"size": 104857600,
"durationSeconds": 120,
"width": 1920, "height": 1080, "fps": 24,
"codec": "H.264", "bitDepth": 8, "chroma": "4:2:0",
"fingerprint": {
"size": 104857600,
"headSha256": "<SHA-256 of the first 4194304 bytes>",
"tailSha256": "<SHA-256 of the last 4194304 bytes>",
"edgeBytes": 4194304
}
}'{
"ok": true,
"id": "est_...",
"credits": {
"total": 60,
"fromPlan": 60,
"fromTrial": 0,
"fromPurchased": 0,
"shortBy": 0
},
"etaMinutes": 6,
"hdr": {"detected": false},
"output": {"format": "FULL_SBS", "width": 3840, "height": 1080}
}Trabajos
POST /v1/jobs con {"estimateId": "est_..."} aprueba el precio. Esto reserva los créditos. La respuesta contiene el id del trabajo y el plan de subida: upload.partSize y las URL de upload.parts.
Opciones de salida
POST /v1/jobs también acepta output: {layout, max_height}.
layout:full_sbs(valor por defecto).max_height:"source"(valor por defecto),2160o1080.
POST /v1/jobs
curl https://api.owl3d.com/v1/jobs \
-H "Authorization: Bearer $OWL3D_API_KEY" \
-H "Content-Type: application/json" \
-d '{"estimateId": "est_...", "output": {"layout": "full_sbs", "max_height": "source"}}'{
"id": "job_...",
"status": "uploading",
"upload": {
"partSize": 67108864,
"parts": ["https://...", "https://..."],
"completeUrl": "/v1/jobs/job_.../upload-complete"
}
}Subir primero
Envía POST /v1/jobs sin estimateId. Tu código no lee el archivo y no crea una huella.
| Campo | Valor |
|---|---|
filename |
El nombre del archivo, 255 caracteres como máximo. Termina en .mp4, .mov, .mkv o .m4v |
sizeBytes |
El tamaño del archivo en bytes |
output |
Opcional. Consulta Opciones de salida |
- El saldo debe cubrir el precio mínimo. Si no, la respuesta es
402 short_of_creditsy no se crea ningún trabajo. - La respuesta
201también traereservedCreditsy"priceAfterUpload": true. - Después de la subida, el servidor lee el archivo y fija el precio.
- Si el servidor no puede convertir el archivo, el trabajo pasa a
failed. No se cobra nada y el archivo se borra.
Un vídeo HDR10 necesita una estimación. Sin ella, la respuesta es 400 hdr_needs_estimate.
POST /v1/jobs (upload first)
curl https://api.owl3d.com/v1/jobs \
-H "Authorization: Bearer $OWL3D_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"filename\": \"my-video.mp4\", \"sizeBytes\": $(wc -c < my-video.mp4)}"{
"id": "job_...",
"status": "uploading",
"upload": {
"partSize": 67108864,
"parts": ["https://...", "https://..."],
"completeUrl": "/v1/jobs/job_.../upload-complete"
},
"reservedCredits": 15,
"priceAfterUpload": true
}Créditos insuficientes
Si el precio supera el saldo, el trabajo pasa a insufficient_credits. No se cobra nada. Esto vale para los dos casos, con o sin estimación.
GET /v1/jobs/{id}muestrashortBy(los créditos que faltan),neededCredits,deleteAtyresumeUrl.- Añade créditos y luego envía
POST /v1/jobs/{id}/resume. No subas el archivo otra vez. - Mientras el saldo siga siendo bajo, resume responde
402 short_of_creditsconshortBy. - Sin resume antes de
deleteAt(24 horas), el trabajo pasa afailedconinsufficient_credits_expired. El archivo se borra.
POST /v1/jobs/{id}/resume
curl -X POST https://api.owl3d.com/v1/jobs/job_.../resume \
-H "Authorization: Bearer $OWL3D_API_KEY"{
"id": "job_...",
"status": "queued"
}{
"error": "short_of_credits",
"shortBy": 120,
"neededCredits": 450,
"deleteAt": "2026-11-04T09:00:00.000Z"
}Subidas
- Dividir el archivo en partes de
upload.partSizebytes. La parte 1 empieza en el byte 0. - Enviar cada parte con
PUTa su URL enupload.parts. Conservar el encabezadoETagde cada respuesta, con sus comillas. - Enviar
POST /v1/jobs/{id}/upload-completecon{"etags": [...]}en el orden de las partes.
Cada URL está firmada para una longitud exacta. Una parte debe tener partSize bytes, y la última parte contiene el resto. El almacenamiento responde 403 para cualquier otra longitud y para un cuerpo chunked. Enviar Content-Length.
Si una subida se detiene, no empezar de nuevo:
- Llamar a
GET /v1/jobs/{id}/upload. - Leer
done. Cada elemento tienepartNumber,etagysize. - Leer
missing. Lista números de parte. - Subir cada parte que falta a
parts[partNumber - 1]. Estas URL son válidas durante 1 hora. - Completar con todos los ETags en el orden de las partes. Usar el mismo archivo.
Completa una subida en 24 horas. Después, el almacenamiento elimina las partes de una subida no completada.
PUT upload.parts[n] · POST /v1/jobs/{id}/upload-complete
split -b 67108864 my-video.mp4 part_
curl -sS -D - -o /dev/null -X PUT --upload-file part_aa "<upload.parts[0]>" | grep -i '^etag'
curl -sS -D - -o /dev/null -X PUT --upload-file part_ab "<upload.parts[1]>" | grep -i '^etag'
curl https://api.owl3d.com/v1/jobs/job_.../upload-complete \
-H "Authorization: Bearer $OWL3D_API_KEY" \
-H "Content-Type: application/json" \
-d '{"etags": ["\"<etag of part 1>\"", "\"<etag of part 2>\""]}'{"id": "job_...", "status": "queued"}GET /v1/jobs/{id}/upload
curl https://api.owl3d.com/v1/jobs/job_.../upload \
-H "Authorization: Bearer $OWL3D_API_KEY"{
"id": "job_...",
"partSize": 67108864,
"parts": ["https://...", "https://..."],
"completeUrl": "/v1/jobs/job_.../upload-complete",
"done": [{"partNumber": 1, "etag": "\"...\"", "size": 67108864}],
"missing": [2]
}Estado del trabajo
GET /v1/jobs/{id} da el estado, el progreso y el tiempo restante. Consultar cada 15 segundos.
status |
Significado |
|---|---|
uploading |
El trabajo espera el archivo |
queued |
El trabajo espera una GPU. Un trabajo puede volver de converting a queued por poco tiempo |
converting |
progress va de 0 a 1 |
delayed |
Retenido de nuestro lado. Los créditos siguen reservados |
insufficient_credits |
El precio supera el saldo. Añade shortBy créditos y luego llama a resume. Consulta Créditos insuficientes |
done |
Listo. result contiene el nombre, el tamaño y la ruta del enlace |
failed |
error contiene un code. Los créditos se devolvieron |
cancelled |
Cancelado por quien llama |
GET /v1/jobs/{id}
curl https://api.owl3d.com/v1/jobs/job_... \
-H "Authorization: Bearer $OWL3D_API_KEY"{
"id": "job_...",
"status": "converting",
"progress": 0.42,
"etaSeconds": 310
}Resultados
GET /v1/jobs/{id}/result da un enlace de descarga para un trabajo done. El enlace funciona 15 minutos y solo para GET. Llamar de nuevo a la ruta para obtener un enlace nuevo. Cuando el resultado expiró, la respuesta es 410.
GET /v1/jobs/{id}/result
curl https://api.owl3d.com/v1/jobs/job_.../result \
-H "Authorization: Bearer $OWL3D_API_KEY"
curl -o output.mp4 "<url>"{
"url": "https://...",
"filename": "...",
"sizeBytes": 209715200,
"expiresInS": 900
}Cancelar
POST /v1/jobs/{id}/cancel cancela un trabajo. Un trabajo cancelado antes de que empiece la conversión no cuesta nada. Después, cuesta el precio x progress, redondeado hacia arriba, con un mínimo de 1 crédito.
POST /v1/jobs/{id}/cancel
curl -X POST https://api.owl3d.com/v1/jobs/job_.../cancel \
-H "Authorization: Bearer $OWL3D_API_KEY"{"id": "job_...", "status": "cancelled"}