video-to-gif API
Pricing: see the model page https://modelfold.ai/pricing
Envía una tarea con tu API Key y recibe el resultado mediante consulta o callback. Consulta la versión inglesa para todos los parámetros de generación.
POST /v1/tools/video-to-gif · GET /v1/tools/video-to-gif/jobs/:id
Authorization: Bearer <API_KEY>
Notificaciones por callback
Añade estos campos al nivel superior del JSON, o como parámetros de consulta al subir un archivo binario. Solo las solicitudes con API Key admiten callbacks; las generaciones desde la web no se suscriben.
| Parámetro | Descripción |
|---|---|
| callBackUrl | Opcional. URL HTTPS pública en el puerto 443, sin credenciales ni fragmentos. Todas las direcciones DNS deben ser públicas. Cada intento vuelve a validar DNS y fija la IP de conexión. No se permiten redirecciones. |
| callbackSecret | Obligatorio con callBackUrl. Secreto aleatorio independiente de 32–256 caracteres, almacenado cifrado y nunca devuelto. No uses tu API Key. |
Ejemplos de envío
curl
curl 'https://modelfold.ai/v1/tools/video-to-gif' \
-H "Authorization: Bearer $MODELFOLD_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"url": "https://example.com/video.mp4",
"duration": 1,
"width": 320,
"fps": 10,
"callBackUrl": "https://your-service.example/webhooks/modelfold",
"callbackSecret": "replace-with-your-own-random-secret-at-least-32-characters"
}'Python
import os, requests
payload = {'url': 'https://example.com/video.mp4', 'duration': 1, 'width': 320, 'fps': 10, 'callBackUrl': 'https://your-service.example/webhooks/modelfold', 'callbackSecret': 'replace-with-your-own-random-secret-at-least-32-characters'}
payload["callbackSecret"] = os.environ["MODELFOLD_CALLBACK_SECRET"]
response = requests.post("https://modelfold.ai/v1/tools/video-to-gif",
headers={"Authorization": "Bearer " + os.environ["MODELFOLD_API_KEY"]},
json=payload, timeout=180)
response.raise_for_status()
print(response.json())JavaScript (Node.js 18+)
const payload = {
"url": "https://example.com/video.mp4",
"duration": 1,
"width": 320,
"fps": 10,
"callBackUrl": "https://your-service.example/webhooks/modelfold",
"callbackSecret": "replace-with-your-own-random-secret-at-least-32-characters"
}
payload.callbackSecret = process.env.MODELFOLD_CALLBACK_SECRET
const response = await fetch('https://modelfold.ai/v1/tools/video-to-gif', {
method: 'POST', headers: { Authorization: `Bearer ${process.env.MODELFOLD_API_KEY}`,
'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
if (!response.ok) throw new Error(await response.text())
console.log(await response.json())POST body
El cuerpo POST tiene la misma estructura que la respuesta de consulta: una instantánea después de la liquidación. callback refleja el momento de encolado; consulta el estado actual de entrega. El ID del evento está en la cabecera, no en el cuerpo.
{
"ok": true,
"id": "123",
"taskId": "123",
"status": "ok",
"url": "/uploads/outputs/result.gif",
"size": 123456,
"latencyMs": 500,
"error": null,
"callback": {
"state": "pending",
"attempts": 0
}
}Verificación de firma (Node.js / Express)
- X-ModelFold-Event-Id
- X-ModelFold-Timestamp: Unix seconds
- X-ModelFold-Signature: Base64(HMAC-SHA256(secret, timestamp + "." + eventId + "." + rawRequestBody))
import express from 'express'
import crypto from 'node:crypto'
const app = express()
// Register before express.json(); verify the original bytes.
app.post('/webhooks/modelfold', express.raw({type:'application/json',limit:'1mb'}), async (req,res) => {
const timestamp = req.get('X-ModelFold-Timestamp') || ''
const eventId = req.get('X-ModelFold-Event-Id') || ''
const received = Buffer.from(req.get('X-ModelFold-Signature') || '', 'base64')
const expected = crypto.createHmac('sha256', process.env.MODELFOLD_CALLBACK_SECRET)
.update(`${timestamp}.${eventId}.`).update(req.body).digest()
const valid = /^\d+$/.test(timestamp) && Math.abs(Date.now()/1000-Number(timestamp)) <= 300
&& eventId && received.length === expected.length && crypto.timingSafeEqual(received,expected)
if (!valid) return res.sendStatus(403)
const result = JSON.parse(req.body.toString('utf8'))
// Implement with your database: UNIQUE(eventId), durable insert before 2xx.
// Duplicate eventId: acknowledge without processing business logic again.
await persistEventOnce(eventId, result)
res.sendStatus(204)
})
app.listen(3000)Devuelve cualquier 2xx en 10 segundos. Los errores de red, tiempos de espera y respuestas no 2xx se reintentan hasta 10 veces en total, con espera exponencial desde 30 segundos hasta una hora. No se siguen redirecciones. El ID y el cuerpo permanecen iguales; cambian la marca de tiempo y la firma. El estado persiste tras reinicios y el éxito confirmado detiene los intentos. Una confirmación perdida puede causar duplicados: deduplica de forma persistente por ID. callback.state es none / pending / delivered / exhausted y callback.attempts cuenta los intentos. Los fallos de entrega no cambian resultados ni cargos.
Response fields and retries
Amount fields are omitted from examples. credits is a numeric response field; read it from the API response after the task completes. On HTTP 409, read the latest response and update expectedCredits before retrying.
Poll with the same API key every 2 seconds. Status is pending, ok or failed. After a disconnection, check the task or request history before submitting again.
