curl --request POST \
--url https://api-prod.studio.getsupervisor.ai/v1/uploads/{uploadId}/confirm \
--header 'Authorization: Bearer <token>' \
--header 'x-workspace-id: <x-workspace-id>'import requests
url = "https://api-prod.studio.getsupervisor.ai/v1/uploads/{uploadId}/confirm"
headers = {
"x-workspace-id": "<x-workspace-id>",
"Authorization": "Bearer <token>"
}
response = requests.post(url, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-workspace-id': '<x-workspace-id>', Authorization: 'Bearer <token>'}
};
fetch('https://api-prod.studio.getsupervisor.ai/v1/uploads/{uploadId}/confirm', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-prod.studio.getsupervisor.ai/v1/uploads/{uploadId}/confirm",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"x-workspace-id: <x-workspace-id>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api-prod.studio.getsupervisor.ai/v1/uploads/{uploadId}/confirm"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("x-workspace-id", "<x-workspace-id>")
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api-prod.studio.getsupervisor.ai/v1/uploads/{uploadId}/confirm")
.header("x-workspace-id", "<x-workspace-id>")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-prod.studio.getsupervisor.ai/v1/uploads/{uploadId}/confirm")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-workspace-id"] = '<x-workspace-id>'
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"uploadId": "7f3a1b2c-9d4e-4f6a-8b1c-2d3e4f5a6b7c",
"namespace": "knowledge-sources",
"status": "ready",
"originalFileName": "manual-cobranza.pdf",
"contentType": "application/pdf",
"declaredSizeBytes": 348172,
"sizeBytes": 348172,
"rejectedReason": null,
"createdBy": "usr_2f1c",
"createdAt": "2026-09-04T22:30:00.000Z",
"confirmedAt": "2026-09-04T22:31:12.000Z"
}Confirmar que el archivo ya está subido
Cierra el ciclo que abrió el presign: la API va al almacén, mira los bytes y decide si el archivo vale. Hasta que esto ocurre, lo subido no es utilizable por nadie.
Comprueba dos cosas que el cliente no puede acreditar por sí mismo: cuánto ocupa el
objeto de verdad —de ahí que sizeBytes pueda no coincidir con declaredSizeBytes—, y
que su contenido corresponde a su extensión. Un .pdf que no empieza por %PDF se
rechaza aunque el nombre diga otra cosa.
Es idempotente sobre lo que salió bien: confirmar un archivo que ya está ready
devuelve 200 con el mismo cuerpo y no vuelve a tocar el almacén. Lo es a propósito,
porque el cliente reintenta esta llamada cuando la red le falla a mitad.
Esa red de seguridad no cubre el rechazo. Un rejected es terminal: no admite un
segundo confirm, así que si el 422 se perdió por el camino, el reintento no vuelve a
dártelo. Para saber cómo acabó una subida cuyo confirm no llegó a contestar, usa el
GET, que responde el estado y el rejectedReason.
Un rechazo se persiste antes de responder: el archivo queda en rejected con su
rejectedReason, no se pierde el motivo. Por eso responde 422 y no 400 — la
petición era válida; lo que no valía era el contenido.
curl --request POST \
--url https://api-prod.studio.getsupervisor.ai/v1/uploads/{uploadId}/confirm \
--header 'Authorization: Bearer <token>' \
--header 'x-workspace-id: <x-workspace-id>'import requests
url = "https://api-prod.studio.getsupervisor.ai/v1/uploads/{uploadId}/confirm"
headers = {
"x-workspace-id": "<x-workspace-id>",
"Authorization": "Bearer <token>"
}
response = requests.post(url, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-workspace-id': '<x-workspace-id>', Authorization: 'Bearer <token>'}
};
fetch('https://api-prod.studio.getsupervisor.ai/v1/uploads/{uploadId}/confirm', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-prod.studio.getsupervisor.ai/v1/uploads/{uploadId}/confirm",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"x-workspace-id: <x-workspace-id>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api-prod.studio.getsupervisor.ai/v1/uploads/{uploadId}/confirm"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("x-workspace-id", "<x-workspace-id>")
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api-prod.studio.getsupervisor.ai/v1/uploads/{uploadId}/confirm")
.header("x-workspace-id", "<x-workspace-id>")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-prod.studio.getsupervisor.ai/v1/uploads/{uploadId}/confirm")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-workspace-id"] = '<x-workspace-id>'
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"uploadId": "7f3a1b2c-9d4e-4f6a-8b1c-2d3e4f5a6b7c",
"namespace": "knowledge-sources",
"status": "ready",
"originalFileName": "manual-cobranza.pdf",
"contentType": "application/pdf",
"declaredSizeBytes": 348172,
"sizeBytes": 348172,
"rejectedReason": null,
"createdBy": "usr_2f1c",
"createdAt": "2026-09-04T22:30:00.000Z",
"confirmedAt": "2026-09-04T22:31:12.000Z"
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Headers
Identificador del workspace multi-tenant.
Path Parameters
La fila que dejó el presign. El namespace no viaja en estas rutas: sale de la fila, porque consultarlo antes de autorizar exigiría leer la base para decidir el permiso, y eso filtraría si un id existe a quien no debería saberlo.
Response
Archivo verificado y utilizable. También es la respuesta cuando ya lo estaba.
Lo que se sabe de un archivo subido, mirado desde fuera.
Destino del archivo. Es un conjunto cerrado y versionado en el código: cada namespace trae su propia política de formatos, tamaños y permisos.
agent-briefs, knowledge-sources En qué punto del ciclo está el archivo.
pending es la fila que dejó el presign: existe antes de que se suba un solo byte, y
caduca a las 24 h si nadie la confirma. ready es el archivo verificado —sigue siendo
operable: se puede reclamar y se puede descartar—. rejected, expired y discarded
son estados de los que ya no se sale.
pending, ready, rejected, expired, discarded El nombre con el que el cliente lo mandó, sin normalizar.
El tamaño que el cliente dijo al pedir el presign.
El tamaño real del objeto, medido al confirmar. Es null mientras nadie lo haya
mirado — está aparte de declaredSizeBytes precisamente porque pueden no coincidir.
Por qué se rechazó, cuando status es rejected. Si no, null.
Es un código, no una frase: hoy size-mismatch (lo medido no cuadra con lo
declarado) o content-mismatch (los bytes no son del formato que dice la
extensión). El detalle legible va en el mensaje del 422, no aquí.
"content-mismatch"
