Pular navegação

Exemplo de Implementação do Protocolo Tus

Atualizado: 07.04.2026
Abrir como Markdown

Tus é um protocolo aberto para uploads de arquivos retomáveis. O protocolo permite retomar uploads após uma queda de conexão, enviar arquivos grandes em partes e controlar o processo de upload.

Quando você precisa do Tus

Use o Tus se você estiver fazendo upload de arquivos grandes e quiser:

  • Retomar uploads após uma queda de rede — o usuário não perderá o progresso
  • Enviar o arquivo em partes (chunk upload) — mais confiável para arquivos grandes
  • Mostrar progresso para o usuário e gerenciar tentativas

Para desenvolvedores: Documentação oficial do protocolo: https://tus.io/protocols/resumable-upload.html
Bibliotecas para diferentes linguagens: https://tus.io/implementations.html

Como funciona o upload Tus (4 passos)

  1. O cliente seleciona um arquivo e faz uma solicitação ao seu backend (por exemplo, POST /upload), passando o nome do arquivo e o tamanho (sem o arquivo em si).
  2. O backend chama a API do Kinescope POST /v2/init e recebe um endpoint Tus único em resposta.
  3. O backend retorna este endpoint para o cliente (como redirecionamento ou em JSON).
  4. O cliente faz upload do arquivo via protocolo Tus diretamente para o Kinescope usando o endpoint recebido.
Importante: O backend não participa da transferência de arquivos. O arquivo e o tráfego principal de upload não passam pelo seu servidor: o backend é necessário apenas para init (para obter um endpoint Tus único), e então o navegador faz o upload do arquivo diretamente para o Kinescope via endpoint emitido.

O que preparar

Antes de começar, certifique-se de ter:

  • Token de API do Kinescope (armazene no servidor, não publique no navegador)
  • ID do projeto ou pasta (parent_id) para onde o arquivo será enviado
  • Seu endpoint de backend, por exemplo, POST /upload, que irá:
    • aceitar metadados do arquivo do cliente
    • chamar https://uploader.kinescope.io/v2/init
    • retornar o endpoint Tus para o cliente
Importante: O token do Kinescope não deve ser passado para o frontend. O cliente deve se comunicar apenas com seu backend e o endpoint Tus.

Diagrama de interação

Aqui está como parece o processo de upload:

sequenceDiagram
    participant Client as Navegador_Cliente
    participant Backend as Backend_ServidorUsuario
    participant API as API_Kinescope
    participant TusEndpoint as Endpoint_Tus_Kinescope

    Note over Backend: "Arquivo não passa pelo backend"
    Client->>Backend: "POST /upload (metadados, tamanho)"
    Backend->>API: "POST /v2/init (Bearer TOKEN, parent_id, filename, filesize, type)"
    API-->>Backend: "201 Created (endpoint)"
    Backend-->>Client: "endpoint (Location/redirect ou JSON)"

    Note over Client,TusEndpoint: "Arquivo é enviado diretamente para o Kinescope"
    Client->>TusEndpoint: "PATCH chunk_1 (bytes do arquivo)"
    TusEndpoint-->>Client: "204 No Content (Upload-Offset)"
    Client->>TusEndpoint: "PATCH chunk_2 (bytes do arquivo)"
    TusEndpoint-->>Client: "204 No Content (Upload-Offset)"
    Note over Client,TusEndpoint: "Repete até o upload estar completo"
    Client->>TusEndpoint: "PATCH ultimo_chunk (bytes do arquivo)"
    TusEndpoint-->>Client: "204 No Content (Upload completo)"

O contrato do método /upload

O que o cliente envia → seu backend

Se você usa tus-js-client, uma opção conveniente é aceitar cabeçalhos Tus padrão:

  • Upload-Length: tamanho do arquivo em bytes
  • Upload-Metadata: metadados (por exemplo, filename em base64)

Para desenvolvedores: Nos exemplos abaixo, os metadados são obtidos do cabeçalho Upload-Metadata e o tamanho de Upload-Length. Esta não é a única opção: esses valores também podem ser aceitos em JSON se for mais conveniente para seu frontend.

O que seu backend retorna → cliente

Existem duas opções funcionais:

  • Opção A (como nos exemplos abaixo): Resposta 201 Created + cabeçalho Location: <tus-endpoint>, ou redirecionamento para endpoint
  • Opção B: 200 OK + JSON { "endpoint": "<tus-endpoint>" } (e no frontend você usa uploadURL)

Se você usar Opção A, certifique-se de que CORS permite ao cliente ler o cabeçalho Location (Access-Control-Expose-Headers: Location é necessário).

Exemplo de solicitação para /v2/init do Kinescope

Seu backend deve enviar uma solicitação de inicialização de upload:

curl -X POST 'https://uploader.kinescope.io/v2/init' \
  -H 'Authorization: Bearer <KINESCOPE_API_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "parent_id": "<PROJECT_OR_FOLDER_ID>",
    "type": "video",
    "filename": "example.mp4",
    "title": "example.mp4",
    "filesize": 123456789
  }'

Em resposta, o Kinescope retornará 201 Created e um objeto contendo data.endpoint — este é o endpoint Tus para upload.

Exemplo de implementação de backend

Aqui está um exemplo de handler de backend em Go:

package main

import (
    "encoding/base64"
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"
    "strings"
)

const (
    kinescopeAPIToken      = "11111111-1111-1111-1111-111111111111"
    kinescopeUploadInitURL = "https://uploader.kinescope.io/v2/init"
)

type KinescopeInitResponse struct {
    Data struct {
        ID       string `json:"id"`
        Endpoint string `json:"endpoint"`
    } `json:"data"`
}

// Handler para solicitação de inicialização de upload
func handleUploadInit(w http.ResponseWriter, r *http.Request) {
    origin := r.Header.Get("Origin")
    
    // Definir cabeçalhos CORS
    if origin != "" {
        w.Header().Set("Access-Control-Allow-Origin", origin)
        w.Header().Set("Access-Control-Allow-Credentials", "true")
        w.Header().Set("Access-Control-Allow-Headers", 
            "Origin, Content-Type, Tus-Resumable, Upload-Length, Upload-Metadata")
        w.Header().Set("Access-Control-Allow-Methods", 
            "POST, GET, HEAD, PATCH, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Expose-Headers", "Location")
    }
    
    // Manipular solicitação OPTIONS
    if r.Method == "OPTIONS" {
        w.Header().Set("Access-Control-Max-Age", "86400")
        w.WriteHeader(http.StatusOK)
        return
    }
    
    // Analisar metadados do cabeçalho Upload-Metadata
    metadata := parseMetadataHeader(r.Header.Get("Upload-Metadata"))
    
    // Analisar tamanho do arquivo do cabeçalho Upload-Length
    filesize, err := strconv.ParseInt(r.Header.Get("Upload-Length"), 10, 64)
    if err != nil || filesize <= 0 {
        http.Error(w, "bad header Upload-Length", http.StatusBadRequest)
        return
    }
    
    // Construir solicitação para a API do Kinescope
    requestBody := map[string]interface{}{
        "client_ip": r.RemoteAddr,
        "parent_id": "seu ID de projeto ou pasta aqui",
        "type":      "video",
        "title":     metadata["filename"],
        "filename":  metadata["filename"],
        "filesize":  filesize,
    }
    
    // Chamar a API do Kinescope para inicializar upload
    body, _ := json.Marshal(requestBody)
    req, _ := http.NewRequest("POST", kinescopeUploadInitURL, strings.NewReader(string(body)))
    req.Header.Set("Authorization", "Bearer "+kinescopeAPIToken)
    req.Header.Set("Content-Type", "application/json")
    
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil || resp.StatusCode != http.StatusCreated {
        http.Error(w, fmt.Sprintf("kinescope api response status=%d", resp.StatusCode), 
            http.StatusBadRequest)
        return
    }
    defer resp.Body.Close()
    
    var result KinescopeInitResponse
    json.NewDecoder(resp.Body).Decode(&result)
    
    // Retornar redirecionamento para endpoint Tus
    w.Header().Set("Location", result.Data.Endpoint)
    w.WriteHeader(http.StatusCreated)
}

// Analisar cabeçalho Upload-Metadata
func parseMetadataHeader(header string) map[string]string {
    meta := make(map[string]string)
    
    if header == "" {
        return meta
    }
    
    elements := strings.Split(header, ",")
    for _, element := range elements {
        parts := strings.Fields(strings.TrimSpace(element))
        if len(parts) != 2 {
            continue
        }
        
        decoded, err := base64.StdEncoding.DecodeString(parts[1])
        if err != nil {
            continue
        }
        meta[parts[0]] = string(decoded)
    }
    
    return meta
}

Exemplo de implementação de frontend

Use a biblioteca tus-js-client para trabalhar com o protocolo Tus no navegador.

Exemplo HTML:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Demo Upload Tus</title>
  </head>
  <body>
    <input type="file" id="file-input">
  </body>
  <script src="https://cdn.jsdelivr.net/npm/tus-js-client@latest/dist/tus.js"></script>
  <script src="upload.js"></script>
</html>

Exemplo de código JavaScript (upload.js):

function initializeUpload(file, backendEndpoint) {
  const upload = new tus.Upload(file, {
    // Opção 1: endpoint no seu backend (recomendado)
    endpoint: backendEndpoint,
    // Opção 2: uploadURL direto (se você já tiver um endpoint Tus)
    // uploadURL: "https://uploader.kinescope.io/v2/upload/0966958f-638b-4aab-bf4a-7f9860a57a93",
    
    retryDelays: [0, 3000, 5000, 10000, 20000],
    chunkSize: 10000000,  // 10 MB por parte
    
    metadata: {
      filename: file.name,
      filetype: file.type
    },
    
    onError: function(error) {
      console.error("Erro de upload:", error);
    },
    
    onProgress: function(bytesUploaded, bytesTotal) {
      const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2);
      console.log(`Enviado: ${bytesUploaded} / ${bytesTotal} (${percentage}%)`);
    },
    
    onSuccess: function() {
      console.log(`Arquivo ${upload.file.name} enviado com sucesso. URL: ${upload.url}`);
    }
  });
  
  // Encontrar uploads anteriores para retomar
  upload.findPreviousUploads()
    .then(function(previousUploads) {
      if (previousUploads.length > 0) {
        upload.resumeFromPreviousUpload(previousUploads[0]);
      }
      upload.start();
    })
    .catch(function(error) {
      console.error("Erro ao encontrar uploads anteriores:", error);
      upload.start();
    });
}

document.addEventListener('DOMContentLoaded', function() {
  const fileInput = document.getElementById('file-input');
  
  if (!fileInput) {
    console.error('Elemento file-input não encontrado');
    return;
  }
  
  fileInput.addEventListener('change', function(event) {
    const file = event.target.files[0];
    
    if (!file) {
      return;
    }
    
    const backendEndpoint = 'https://seu-backend.com/upload';
    
    initializeUpload(file, backendEndpoint);
  });
});

Solução de problemas

Erro CORS e cabeçalho Location não visível

Problema: Erro CORS no navegador e o cabeçalho Location não está visível.

Solução: Verifique se seu backend define Access-Control-Expose-Headers: Location.

403/401 do Kinescope ao chamar /v2/init

Problema: O Kinescope retorna um erro de autorização.

Solução: Verifique o token e os direitos de acesso, certifique-se de que o token não está expirado ou revogado.

Upload não continua após uma queda

Problema: Após uma queda de conexão, o upload não retoma.

Solução: Habilite findPreviousUploads()/resumeFromPreviousUpload() e não altere endpoint/uploadURL para o mesmo arquivo.

Erros de rede frequentes

Problema: Erros de rede constantes durante o upload.

Solução: Reduza chunkSize e configure retryDelays.

O que enviar ao suporte

Se você precisar de ajuda do suporte técnico, anexe:

Canal de suporte: o chat de suporte na interface do Kinescope.

Pronto! Agora você pode configurar o upload de arquivos grandes via protocolo Tus.

O que fazer a seguir?

  1. Upload de arquivos via API — outros métodos para fazer upload de vídeos
  2. Regras gerais da API — autorização e formato de solicitação
  3. Kinescope API — documentação completa da API

Ainda tem dúvidas? Escreva para o chat de suporte na interface do Kinescope — nossos especialistas vão ajudar!