Aller au contenu principal

Ajouter un fichier par l'API

Ajoutez une vidéo ou n'importe quel fichier à votre bibliothèque avec POST /v1/uploads : en un seul PUT ou en reprenable avec tus, depuis votre serveur ou directement depuis le navigateur, puis suivez-le jusqu'à ce qu'il soit prêt.

Ajouter un fichier à la bibliothèque de votre organisation se fait en deux temps : vous ouvrez un upload avec POST /v1/uploads, puis vous envoyez les octets à l’adresse qu’il vous renvoie. L’asset existe dès le premier temps — vous avez son identifiant tout de suite — et devient lisible une fois le fichier arrivé et traité.

Le fichier est déjà en ligne, dans votre bucket ou sur votre serveur de médias ? Passez le second temps : donnez-nous son lien, nous le téléchargeons pour vous. C’est une vidéo publiée sur YouTube, Vimeo ou Dailymotion ? Il n’y a pas de fichier à ajouter : POST /v1/assets crée un asset qui la lit depuis le site de son fournisseur.

Avant de commencer

  • Une clé qui peut ajouter des fichiers. Dans l’application Videas, ouvrez Paramètres → Accès API et créez une clé. Sous Ce que la clé peut faire, cochez Ajouter des fichiers à la bibliothèque par l’API : la case est décochée par défaut, pour qu’une clé qui écrit soit toujours créée délibérément. Cochez aussi Lire la bibliothèque par l’API : il vous la faut pour lister les workspaces, suivre l’asset et relancer sans risque.
  • Un workspace : l’id de l’un de ceux que renvoie GET /v1/workspaces. L’asset arrive à sa racine.

Chaque appel part vers https://api.videas.com/v1, avec la clé en jeton Bearer :

Authorization: Bearer ak_…

Attention : la clé reste sur votre serveur. /v1 n’envoie aucun en-tête CORS, un navigateur ne peut donc pas l’appeler de toute façon ; une page qui envoie un fichier utilise le jeton propre à l’upload — voir Depuis le navigateur.

Étape 1 — ouvrir l’upload

curl -X POST https://api.videas.com/v1/uploads \
  -H "Authorization: Bearer $VIDEAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "workspace_id": "0192a6b1-7c3e-7d40-9a51-3f0e2b6c8d10",
    "filename": "cours-magistral.mp4",
    "size_bytes": 734003200,
    "name": "Cours d’ouverture",
    "external_ref": "cours-42/seance-1"
  }'
Champ
workspace_id Obligatoire. Où l’asset arrive.
filename Obligatoire. Le nom du fichier chez vous, extension comprise — c’est d’abord de lui qu’on déduit la nature de l’asset.
size_bytes Obligatoire. La taille exacte du fichier en octets, 10 Gio au plus.
content_type Facultatif. Noté, jamais cru sur parole : le fichier est analysé une fois arrivé.
name, description, language Facultatifs. Le titre de l’asset (le nom du fichier à défaut), sa description et sa langue en balise BCP 47 (fr, en-GB). Le titre fait 500 caractères au plus, la description 50 000.
external_ref Facultatif, et recommandé : votre propre identifiant de l’asset, gardé sans les espaces qui l’entourent et de 500 caractères au plus. C’est lui qui rend une relance sûre.

La réponse est un 201 :

{
  "id": "0192a6b2-4f1d-7e21-b3c8-6d2f0a9e1c57",
  "asset_id": "0192a6b2-4f1a-7b90-8e0d-51c3a7f2b046",
  "workspace_id": "0192a6b1-7c3e-7d40-9a51-3f0e2b6c8d10",
  "source": "client",
  "status": "pending",
  "size_bytes": 734003200,
  "received_bytes": 0,
  "upload_url": "https://api.videas.com/api/visitor/uploads/0192a6b2-4f19-7c02-a4e5-9b8d3c1f6e20",
  "max_put_bytes": 52428800,
  "expires_at": "2026-09-27T09:12:44Z",
  "source_url": null,
  "failure": null,
  "created_at": "2026-09-26T09:12:44Z",
  "updated_at": "2026-09-26T09:12:44Z",
  "finished_at": null,
  "upload_token": "…"
}
  • asset_id désigne l’asset, qui existe déjà, à l’état uploading.
  • id désigne l’upload : gardez-le pour le suivre.
  • upload_url et upload_token disent où et comment envoyer le fichier. Le jeton n’est donné qu’une fois, ici : aucune lecture ultérieure ne le renvoie. upload_url est opaque — utilisez-la telle quelle, ne la construisez jamais.

Étape 2 — envoyer le fichier

Chaque requête vers upload_url porte le jeton dans X-Upload-Token — jamais votre clé API.

Jusqu’à max_put_bytes : un seul PUT

curl -T cours-magistral.mp4 "$UPLOAD_URL" -H "X-Upload-Token: $UPLOAD_TOKEN"
# → 204 No Content
#   X-Checksum-Sha256: 5e8f…   (le SHA-256 de ce qui a été stocké)

Le corps est le fichier entier, Content-Length sa taille exacte — curl s’en charge — et n’importe quel Content-Type convient. L’upload se termine dans cette même requête. Si elle est coupée, rien n’est enregistré : renvoyez le même PUT. Un 409 portant Upload-Offset signifie que l’upload a déjà reçu des octets — si ce décalage égale la taille, le fichier est déjà là.

Toutes tailles : tus, reprenable

Au-delà de max_put_bytes, envoyez le fichier à la même upload_url avec le cœur de tus 1.0 : un HEAD indique où reprendre, chaque PATCH envoie un chunk. Un chunk fait au plus max_put_bytes, et au moins 5 Mio sauf le dernier. N’importe quel client tus standard s’en charge, avec l’adresse et l’en-tête — par exemple tuspy :

from tusclient import client

tus = client.TusClient(upload["upload_url"], headers={"X-Upload-Token": upload["upload_token"]})
uploader = tus.uploader(file_path="cours-magistral.mp4", url=upload["upload_url"], chunk_size=32 * 1024 * 1024)
uploader.upload()  # après une coupure, relancez-le : il reprend au décalage du serveur

La réponse au dernier chunk porte elle aussi X-Checksum-Sha256.

Depuis le navigateur

Vos utilisateurs peuvent envoyer leurs fichiers directement de leur navigateur vers Videas, sans que le fichier transite par votre serveur — et sans que votre clé le quitte. Votre serveur ouvre l’upload ; la page ne reçoit que son adresse et son jeton.

1. Votre serveur ouvre l’upload et transmet à la page ce dont elle a besoin :

// serveur — POST /uploads { filename, size }
app.post('/uploads', async (req, res) => {
  const answer = await fetch('https://api.videas.com/v1/uploads', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.VIDEAS_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      workspace_id: process.env.VIDEAS_WORKSPACE_ID,
      filename: req.body.filename,
      size_bytes: req.body.size,
    }),
  })
  const upload = await answer.json()
  if (!answer.ok) return res.status(answer.status).json(upload)
  res.json({
    upload_url: upload.upload_url,
    upload_token: upload.upload_token,
    max_put_bytes: upload.max_put_bytes,
  })
})

2. La page envoie le fichier — en un seul PUT s’il est assez petit, avec tus-js-client sinon :

<script type="module">
  import * as tus from 'https://cdn.jsdelivr.net/npm/tus-js-client@4/+esm'

  const file = input.files[0]
  const upload = await (await fetch('/uploads', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ filename: file.name, size: file.size }),
  })).json()

  if (file.size <= upload.max_put_bytes) {
    const sent = await fetch(upload.upload_url, {
      method: 'PUT',
      headers: { 'X-Upload-Token': upload.upload_token },
      body: file,
    })
    if (sent.status !== 204) console.error('refusé', sent.status)
  } else {
    new tus.Upload(file, {
      uploadUrl: upload.upload_url,   // l'upload est déjà ouvert : pas de requête de création
      headers: { 'X-Upload-Token': upload.upload_token },
      chunkSize: 32 * 1024 * 1024,    // au plus max_put_bytes, au moins 5 Mio
      retryDelays: [0, 3000, 10000],
      onProgress: (sent, total) => console.log(`${Math.round((sent / total) * 100)} %`),
      onSuccess: () => console.log('stocké'),
      onError: (error) => console.error(error),
    }).start()
  }
</script>

upload_url accepte les requêtes de n’importe quelle origine. Le jeton ouvre à qui le détient cet upload-là et rien d’autre, jusqu’à ce que l’upload se termine.

Le suivre jusqu’à ce qu’il soit prêt

Il n’y a pas encore de notification : interrogez l’upload à intervalles réguliers.

curl https://api.videas.com/v1/uploads/$UPLOAD_ID \
  -H "Authorization: Bearer $VIDEAS_API_KEY"
status Signification
pending Ouvert, rien reçu encore.
receiving Les octets arrivent — received_bytes dit combien.
completed Le fichier est stocké. L’asset passe à processing, puis ready.
cancelled Abandonné : par DELETE /v1/uploads/{id}, ou par la terminaison tus sur upload_url.
expired Le fichier n’était pas complet avant expires_at.

cancelled et expired sont définitifs, et l’asset finit alors en error. Une fois l’upload completed, suivez l’asset lui-même avec GET /v1/assets/{asset_id} : son status passe de uploading à processing, puis ready — ou error si le fichier est inexploitable. Ne le lisez ni ne l’intégrez avant ready.

Pour abandonner un upload, DELETE /v1/uploads/{id} répond 204 ; sur un upload terminé, il répond 409, le fichier appartenant désormais à l’asset.

Relancer sans risque

POST /v1/uploads n’est pas idempotent : envoyé deux fois, il crée deux assets. Une requête qui a expiré ou répondu une 5xx peut pourtant en avoir créé un. Donc :

  1. renseignez external_ref à chaque upload ;

  2. après une expiration ou une 5xx, cherchez-le avant de renvoyer la requête — avec une clé qui lit la bibliothèque :

    curl "https://api.videas.com/v1/assets?external_ref=cours-42%2Fseance-1" \
      -H "Authorization: Bearer $VIDEAS_API_KEY"
    # → { "data": [ … ], "next": null }
  3. rien trouvé : renvoyez-la. Trouvé : la première requête a bien créé l’asset — mais son jeton était dans la réponse perdue, son fichier ne peut donc plus être envoyé. Ouvrez un nouvel upload ; le premier asset finit en error à l’expiration de son upload. Un asset en error sans fichier est une tentative abandonnée de ce genre.

Les requêtes qui envoient les octets n’ont besoin de rien de tout cela : un PUT échoué n’a rien enregistré, et tus reprend là où le serveur l’indique.

Limites

Limite Valeur
Taille d’un fichier 10 Gio
Un PUT, un chunk tus max_put_bytes — 50 Mio
Un chunk tus, sauf le dernier 5 Mio au moins
Délai pour envoyer le fichier jusqu’au expires_at de l’upload

Erreurs

Toutes les erreurs ont le même corps — {"code": "…", "message": "…", "request_id": "…"}. Testez code ; message s’adresse à un humain et peut changer.

Statut code À l’ouverture d’un upload
400 validation size_bytes et source_url ensemble, ou aucun des deux ; pas de filename ; un champ vide ; un name ou une external_ref de plus de 500 caractères, ou une description de plus de 50 000 ; une language mal formée. Le message nomme le champ.
401 unauthorized Pas de clé, ou une clé inconnue, expirée ou révoquée.
403 forbidden La clé ne peut pas ajouter de fichiers à la bibliothèque.
404 not_found Aucun workspace de votre organisation n’a cet identifiant.
413 payload_too_large size_bytes dépasse 10 Gio.
429 rate_limited Trop de requêtes : attendez le nombre de secondes indiqué par Retry-After.

Pour aller plus loin