curl --request POST \
--url https://api-prod.studio.getsupervisor.ai/v1/uploads/{namespace}/from/{uploadId} \
--header 'Authorization: Bearer <token>' \
--header 'x-workspace-id: <x-workspace-id>'import requests
url = "https://api-prod.studio.getsupervisor.ai/v1/uploads/{namespace}/from/{uploadId}"
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/{namespace}/from/{uploadId}', 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/{namespace}/from/{uploadId}",
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/{namespace}/from/{uploadId}"
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/{namespace}/from/{uploadId}")
.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/{namespace}/from/{uploadId}")
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": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"namespace": "agent-briefs",
"status": "pending",
"originalFileName": "<string>",
"contentType": "<string>",
"declaredSizeBytes": 123,
"sizeBytes": 123,
"rejectedReason": "content-mismatch",
"createdBy": "<string>",
"createdAt": "2023-11-07T05:31:56Z",
"confirmedAt": "2023-11-07T05:31:56Z"
}{
"code": "<string>",
"message": "<string>",
"details": {
"subcode": "<string>",
"workspaceId": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"code": "<string>",
"message": "<string>",
"details": {
"subcode": "<string>",
"workspaceId": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"code": "<string>",
"message": "<string>",
"details": {
"subcode": "<string>",
"workspaceId": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"code": "<string>",
"message": "<string>",
"details": {
"subcode": "<string>",
"workspaceId": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"code": "UPLOAD_REJECTED_BY_NAMESPACE",
"message": "<string>",
"details": {
"fileName": "<string>",
"namespace": "<string>"
}
}{
"code": "UPLOAD_REJECTED_BY_NAMESPACE",
"message": "<string>",
"details": {
"fileName": "<string>",
"namespace": "<string>"
}
}Usar un archivo ya subido también en otro destino
Copia un archivo que el cliente ya subió y confirmó a otro namespace, sin volver a subirlo: los bytes se duplican dentro del almacén y no pasan por la API.
El caso que lo motiva: alguien adjunta un tarifario mientras conversa con el asistente
que le crea su agente —eso vive en agent-briefs— y ese mismo tarifario tiene que acabar
siendo una base de conocimiento, que consume knowledge-sources.
No se comparte la fila, se duplica, y no es por comodidad: una subida tiene un solo reclamante y los dos destinos tienen retenciones distintas. Con una sola fila, borrar el agente soltaría el documento de una base que quizá consultan otros agentes.
Manda la política del destino, no la del origen. agent-briefs admite .md, .csv
y hasta 10 MiB; knowledge-sources sólo .pdf, .docx, .txt y hasta 2 MiB —ese tope
es del proveedor que indexa, no nuestro—. Un archivo que no cabe se rechaza nombrándolo,
para que quien llama pueda explicarle al cliente por qué su documento no sirvió para eso.
No es idempotente, igual que el presign: cada llamada deja una fila. Una copia que nadie reclame se la lleva el barrido a las 72 h, así que un reintento cuesta un objeto temporal y no un huérfano permanente.
Exige los mismos permisos que subir a ese destino directamente: el scope del namespace
y uploads:write. Por eso el destino va en la ruta y no en el cuerpo.
curl --request POST \
--url https://api-prod.studio.getsupervisor.ai/v1/uploads/{namespace}/from/{uploadId} \
--header 'Authorization: Bearer <token>' \
--header 'x-workspace-id: <x-workspace-id>'import requests
url = "https://api-prod.studio.getsupervisor.ai/v1/uploads/{namespace}/from/{uploadId}"
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/{namespace}/from/{uploadId}', 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/{namespace}/from/{uploadId}",
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/{namespace}/from/{uploadId}"
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/{namespace}/from/{uploadId}")
.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/{namespace}/from/{uploadId}")
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": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"namespace": "agent-briefs",
"status": "pending",
"originalFileName": "<string>",
"contentType": "<string>",
"declaredSizeBytes": 123,
"sizeBytes": 123,
"rejectedReason": "content-mismatch",
"createdBy": "<string>",
"createdAt": "2023-11-07T05:31:56Z",
"confirmedAt": "2023-11-07T05:31:56Z"
}{
"code": "<string>",
"message": "<string>",
"details": {
"subcode": "<string>",
"workspaceId": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"code": "<string>",
"message": "<string>",
"details": {
"subcode": "<string>",
"workspaceId": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"code": "<string>",
"message": "<string>",
"details": {
"subcode": "<string>",
"workspaceId": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"code": "<string>",
"message": "<string>",
"details": {
"subcode": "<string>",
"workspaceId": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"code": "UPLOAD_REJECTED_BY_NAMESPACE",
"message": "<string>",
"details": {
"fileName": "<string>",
"namespace": "<string>"
}
}{
"code": "UPLOAD_REJECTED_BY_NAMESPACE",
"message": "<string>",
"details": {
"fileName": "<string>",
"namespace": "<string>"
}
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Headers
Identificador del workspace multi-tenant.
Path Parameters
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 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
La copia existe y está verificada. Es una subida nueva, con su propio uploadId y su
propio ciclo de vida: hay que reclamarla como cualquier otra o el barrido se la lleva.
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"
