Subir archivos a una ficha por MCP
La herramienta file_uploads: sube un archivo de tu disco a un gasto, una factura, una tarea o un presupuesto, con el permiso y el plan de ese módulo.
Actualizado el . 13 min de lectura
file_uploads («Subir archivos») sube un archivo del disco de la persona a la ficha que lo pide: el justificante de un gasto, un adjunto de una factura o de un presupuesto, la imagen de un proyecto. Pasa por el permiso y el plan del módulo de esa ficha, igual que la subida de la aplicación en esa pantalla, y no necesita el Gestor de archivos.
Úsala cuando otra herramienta de tu servidor pida un fileId y el archivo esté en el disco. Antes había que subirlo con fm_files_manage del servidor utilia-crm, que exige el Gestor de archivos en el plan y su permiso. Para guardar un archivo en una carpeta del Gestor, esa sigue siendo la herramienta.
Requisitos
- Servidor. Está en
utilia-finance,utilia-work,utilia-crm,utilia-marketingyutilia-rrhh, cada uno con sus propios destinos. No está en el servidor de Chat, que no tiene fichas a las que subir, ni en el de desarrolladores, ni en los conectores de los directorios de Claude y ChatGPT. - Scope. No hay scopes nuevos. La conexión necesita el scope de escritura de la operación que va a usar el archivo, o un conjunto que lo incluya: con
finance:payments:createsubes un documento para digitalizar, pero no el justificante de un gasto, que pidefinance:expenses:update. La herramienta aparece entools/listsi la conexión puede abrir alguno de los destinos de ese servidor. - Permiso. Los del destino, todos a la vez, con el alcance de editar esa ficha: los mismos que pide la aplicación para subir ahí. Están en Permisos y scopes de cada destino.
- Plan. El aplicativo del destino: Finanzas, Gestión de proyectos, CRM, Marketing o RRHH. El Gestor de archivos no hace falta.
- Cuenta en pausa. Si la cuenta de la empresa está en pausa, solo funcionan
listyabort_upload.
Cómo se sube
Son tres pasos, reservar, enviar el archivo y confirmar, y después el fileId se pasa a la operación del destino. El archivo nunca viaja dentro de la llamada.
Subir el justificante de un gasto
Participan: Tu asistente, UTILIA OS, Almacén de archivos.
De Tu asistente a UTILIA OS:
request_upload
Con destination y el nombre y el tamaño exacto de cada archivo.Nota en UTILIA OS:
Comprueba el plan, los permisos, el scope y la ficha.De UTILIA OS a Tu asistente:
fileId, uploadUrl y relayUploadUrl (respuesta)
Las dos direcciones valen 15 minutos.De Tu asistente a Almacén de archivos:
PUT del archivo a uploadUrl
Con el Content-Type de la reserva.De Tu asistente a UTILIA OS:
confirm_upload
Con los fileId de la reserva.De UTILIA OS a Tu asistente:
confirmed y nextStep (respuesta)
Mide el tamaño y el tipo reales y dice qué operación usa el fileId.De Tu asistente a UTILIA OS:
expenses document_attach con el fileId
El archivo pasa a ser el justificante del gasto.
La herramienta recibe siempre operation y, según la operación, estos parámetros:
- operationstringobligatorio
- request_upload, confirm_upload, abort_upload, create_upload_link o list.
- destinationobject
- En request_upload y create_upload_link, obligatorio; en list, filtra. type: el destino. id: el identificador de la ficha, salvo en los destinos que no la llevan.
- filesobject[]
- request_upload: de 1 a 20 archivos, cada uno con fileName, fileSize (los bytes exactos) y, si lo sabes, mimeType.
- fileIdsstring[]
- confirm_upload y abort_upload: de 1 a 20 fileId de request_upload.
Reservar la subida
{
"name": "file_uploads",
"arguments": {
"operation": "request_upload",
"destination": { "type": "expense_document", "id": "ckexp0142example" },
"files": [
{ "fileName": "ticket-gasolinera.pdf", "fileSize": 184233, "mimeType": "application/pdf" }
]
}
}La respuesta trae el destino tal como lo verá la persona y, por cada archivo, su fileId y sus dos direcciones:
{
"success": true,
"destination": {
"type": "expense_document",
"id": "ckexp0142example",
"label": "Justificante del gasto",
"recordLabel": "GAS-2026-00142",
"acceptedMimeTypes": ["application/pdf", "image/jpeg", "image/png", "image/webp"],
"maxBytes": 10485760
},
"files": [
{
"fileId": "ckfile0001example",
"fileName": "ticket-gasolinera.pdf",
"method": "PUT",
"uploadUrl": "https://…",
"relayUploadUrl": "https://os.utilia.ai/api/files/relay-upload/…",
"headers": { "Content-Type": "application/pdf" },
"mimeType": "application/pdf",
"expiresInSeconds": 900
}
],
"nextStep": "Envía cada fichero con un PUT a su uploadUrl, con su Content-Type (…). Si el PUT a uploadUrl falla por la red, repítelo a relayUploadUrl; si tu entorno no puede hacer peticiones, pide a la persona que lo suba con create_upload_link. Después llama a file_uploads(operation: \"confirm_upload\", fileIds: [...]) con los fileId de files."
}Cada archivo va por su cuenta: el que no cabe en el destino por su tipo o su tamaño sale en rejected, con su errorCode, y no impide reservar los demás. success llega a false solo si no se ha reservado ninguno. recordLabel es el número de la ficha, nunca un título libre, y llega a null si no lo tiene, como una factura en borrador.
Enviar el archivo
Haz un PUT del archivo a su uploadUrl, con las cabeceras de headers, antes de que pasen los 15 minutos:
curl --fail -sS -T "./ticket-gasolinera.pdf" \
-H "Content-Type: application/pdf" \
"<uploadUrl>"Si el PUT no llega al almacén, tienes dos salidas, que se explican en Si el PUT no llega.
Confirmar y usar el fileId
confirm_upload comprueba que el archivo ha llegado y mide su tamaño y su tipo reales. Si no caben en el destino, lo borra y lo devuelve en failed con su errorCode. Lo que cabe sale en confirmed, y nextStep nombra la operación que recibe ese fileId:
{
"success": true,
"confirmed": [
{
"fileId": "ckfile0001example",
"fileName": "ticket-gasolinera.pdf",
"size": 184233,
"mimeType": "application/pdf",
"destination": { "type": "expense_document", "id": "ckexp0142example" }
}
],
"nextStep": "Pasa cada fileId a expenses(operation: \"document_attach\") con la ficha \"ckexp0142example\"; si esa operación pide confirm, ponlo a true solo cuando la persona lo haya confirmado. Cada fileId vale 24 horas y solo para ese destino."
}La operación del destino recibe el fileId con su contrato de siempre, también su confirm si lo pide. Con un archivo subido así, se salta solo las puertas del Gestor de archivos (verlo, tenerlo en el plan y el scope de lectura de Archivos) y aplica todas las suyas.
Destinos de cada servidor
destination.type solo admite los destinos del servidor al que llamas: el enum de cada copia de la herramienta trae los suyos. Uno de otro servidor responde UPLOAD_DESTINATION_NOT_IN_SERVER, con el servidor donde vive.
| Servidor | destination.type | Para qué | destination.id | Lo usa |
|---|---|---|---|---|
utilia-finance | payment_document | Facturas de proveedores para digitalizar, como en «Subir facturas de proveedores» | No lleva | finance_payments upload |
utilia-finance | expense_document | El justificante principal de un gasto, que sustituye al anterior | El gasto | expenses document_attach |
utilia-finance | expense_attachment | Otro justificante del gasto, sin tocar el principal | El gasto | expenses attachment_link |
utilia-finance | invoice_attachment | Un adjunto de una factura, en cualquier estado | La factura | crm_invoices attachment_link |
utilia-finance | supplier_interaction_attachment | Un adjunto de una interacción con un proveedor | La interacción | suppliers attach_to_interaction |
utilia-work | issue_attachment | Un adjunto de un elemento de trabajo | El elemento | issues_workflow add_attachment |
utilia-work | project_image | El logotipo o la portada de un proyecto | El proyecto | projects update, en logoFileId o coverFileId |
utilia-crm | budget_attachment | Un adjunto de un presupuesto | El presupuesto | crm_budget_files link_attachment o link_attachments_many |
utilia-marketing | marketing_image | Una imagen para los correos de campaña | No lleva | marketing_assets upload |
utilia-rrhh | disconnection_policy | La política de desconexión digital de la empresa | No lleva | attendance disconnection_policy_upload |
destination.id es el identificador interno de la ficha, el que devuelven get y list: el número de un gasto no sirve. En issue_attachment y project_image vale también la clave de la tarea o del proyecto. Si falta en un destino que lo lleva, o sobra en uno que no, la respuesta es UPLOAD_DESTINATION_RECORD_REQUIRED. En los destinos de Gestión de proyectos, label usa los nombres del perfil de trabajo: en Agencia, «Adjunto de un entregable» o «Logotipo o portada de la cuenta».
Permisos y scopes de cada destino
Cada destino pasa sus puertas en este orden al reservar, al confirmar, al crear el enlace y otra vez cuando la operación usa el archivo:
Las puertas de un destino
Tu organización
¿Está su aplicativo en el plan?
El del destino; el Gestor de archivos no cuenta.Si no: MODULE_NOT_ENABLED.
Tu usuario
¿Tiene la persona sus permisos?
Todos los de la fila, no uno de ellos.Si no: PERMISSION_DENIED, con los que faltan.
El token
¿Lleva la conexión su scope?
El de escritura de la operación que usará el archivo.Si no: INSUFFICIENT_SCOPE, con el enlace para concederlo.
La ficha
¿Está la ficha a tu alcance?
Con el alcance de editarla.Si no: UPLOAD_DESTINATION_NOT_FOUND.
La ficha se mira al final, así que una conexión sin el scope no llega a saber si existe. Una ficha que no existe y una que queda fuera de tu alcance dan la misma respuesta.
destination.type | Permisos de la persona | Scope de la conexión |
|---|---|---|
payment_document | PAYMENTS_CREATE | finance:payments:create o finance:payments:update |
expense_document y expense_attachment | EXPENSES_EDIT | finance:expenses:update |
invoice_attachment | INVOICES_EDIT | crm:invoices:update |
supplier_interaction_attachment | SUPPLIERS_INTERACTIONS_MANAGE | finance:suppliers:interactions:manage |
issue_attachment | DEV_ISSUES_EDIT | dev:issues:update |
project_image | DEV_PROJECTS_EDIT y FILE_MANAGER_UPLOAD | dev:projects:update |
budget_attachment | BUDGETS_EDIT | crm:budgets:update |
marketing_image | EMAIL_MARKETING_EDIT | marketing:campaigns:update |
disconnection_policy | ATTENDANCE_MANAGE_POLICIES, con alcance de toda la empresa | hr:attendance:manage |
project_image pide FILE_MANAGER_UPLOAD porque es el permiso con el que la aplicación sube esa imagen, pero no el Gestor de archivos en el plan. confirm_upload, abort_upload y list piden los mismos scopes: solo ves, confirmas y liberas subidas de los destinos que tu conexión puede abrir, y las demás responden como si no existieran. abort_upload no pide el permiso del destino, porque solo libera lo tuyo.
Usar el fileId en su destino
Un fileId de file_uploads vale solo para el destino y la ficha para los que se reservó, una sola vez y durante 24 horas. En otra operación o con otra ficha, la respuesta es UPLOAD_DESTINATION_MISMATCH (409), aunque la persona tenga permiso sobre el Gestor de archivos: hay que subirlo otra vez con el destino que toca. Un fileId del Gestor sigue igual que siempre, con sus puertas.
Cada operación guarda el archivo como lo hace la aplicación en esa pantalla:
- Mismo archivo. El justificante del gasto, el documento para digitalizar, la imagen del proyecto y el adjunto del presupuesto se guardan tal cual, sin copia.
- Copia. En los demás, la operación guarda una copia con el mismo servicio que la subida de la aplicación y la subida de paso se borra. Un adjunto de una factura o de un gasto queda como subido a la ficha, no como un archivo del Gestor enlazado: al quitarlo pasa 30 días por «Eliminados recientemente». El de un elemento de trabajo se adjunta siempre como copia, nunca vinculado.
- Presupuesto. El adjunto nace solo para el equipo: el cliente no lo ve hasta que se comparte, desde la ficha del presupuesto en la aplicación o con
crm_budget_filesupdate_attachment(visibilityyconfirm: true; sinconfirmes un ensayo que no cambia nada). Por MCP, todolink_attachmentnace así, también con un archivo del Gestor, ylist_attachmentsyget_attachmentdevuelvenvisibility. La respuesta lo recuerda ennotice. - Proyecto. La imagen se pone con
projectsupdate, con el mismo proyecto en el destino; el mismofileIdvale paralogoFileIdo paracoverFileId. - Política de desconexión. Se copia a la carpeta del protocolo de RRHH y pasa a ser la vigente.
Lo que no se usa no se queda guardado:
Vida de una subida a una ficha
Reservada
reserved
Tiene fileId y direcciones; falta el archivo o la confirmación.Se llega desde Reservada cuando: abort_upload
Liberada
Estado final
Se borra sin pasar por la papelera.
Se llega desde Reservada cuando: confirm_upload mide el tamaño y el tipo
Confirmada
confirmed
Lista para la operación de su destino.Se llega desde Confirmada cuando: Nadie la usa en 25 horas
Borrada sola
Estado final
Desde las 24 horas ya no se puede usar.
Se llega desde Confirmada cuando: La operación del destino recibe el fileId
Usada
Estado final
Pasa a la ficha y deja de salir en list.
Otras transiciones
- Confirmada pasa a Liberada: abort_upload también libera una confirmada que no se ha usado.
- Reservada pasa a Borrada sola: Una reserva sin confirmar se retira igual.
list devuelve tus subidas sin usar de las últimas 24 horas, como mucho 50, con fileId, fileName, size, mimeType, status (reserved o confirmed), via (mcp o link), destination y createdAt. Admite destination para filtrar. Sirve para recuperar una reserva olvidada y para conocer lo que la persona subió con un enlace.
Si el PUT no llega
Algunos entornos, como el de código de Claude, bloquean la dirección del almacén, y otros, como ChatGPT en la web, no pueden hacer peticiones con un archivo. Para los dos casos hay salida:
¿Cómo llega el archivo?
¿Puede tu entorno hacer peticiones HTTP con el archivo?
Sí:
¿Llega el PUT a uploadUrl?
Sí:
Subida directa
El PUT a uploadUrl y, después, confirm_upload.No:
relayUploadUrl
PUT
El mismo PUT, con las mismas cabeceras, a través de os.utilia.ai.
No:
create_upload_link
Un enlace de 24 horas para que la persona suba el archivo desde su navegador.
relayUploadUrl llega con cada archivo de request_upload. Es el mismo PUT, con el mismo método y las mismas cabeceras, a https://os.utilia.ai/api/files/relay-upload/…. Vale una sola vez y para ese archivo, caduca con la reserva a los 15 minutos y exige que Content-Length sea el tamaño exacto reservado. Después se confirma como siempre. Sus errores están en Subir un archivo cuando el PUT no llega.
create_upload_link recibe destination, con su id si lo lleva, y devuelve uploadLink: url, expiresAt, destination, maxFiles y maxFileBytes. La persona abre el enlace con su sesión de UTILIA OS, ve para qué ficha es y sube los archivos, con los tipos y el tamaño del destino; cada archivo pasa las mismas puertas. Cuando diga que lo ha subido, list con el mismo destination te da los fileId, ya confirmados y con via: "link", y los pasas a la operación del destino.
Solo abre el enlace quien lo pidió, y cada persona puede tener 10 enlaces vivos. Si tu conexión no puede enviar archivos y pides una reserva, la respuesta trae en hint la pista de liberarla con abort_upload y pedir el enlace con el mismo destino.
Límites
Límites de la subida a una ficha
- Archivos por llamada
- 20
- Algunos destinos admiten menos.
- Vida de uploadUrl y relayUploadUrl
- 15minutos
- Vida de un fileId sin usar
- 24horas
- A las 25 horas se borra solo.
- Enlaces de subida vivos por persona
- 10
Cada destino tiene además su tope de archivos por llamada, de tamaño y de tipos, que es el mismo en request_upload y en el enlace para la persona:
destination.type | Archivos por llamada | Tamaño por archivo | Tipos |
|---|---|---|---|
payment_document | 20 | 25 MB | PDF, PNG, JPEG o WebP |
expense_document | 1 | 10 MB | PDF, JPEG, PNG o WebP |
expense_attachment | 20 | 50 MB | Cualquiera |
invoice_attachment | 20 | 50 MB | Cualquiera |
supplier_interaction_attachment | 10 | 10 MB | Cualquiera |
issue_attachment | 20 | 10 MB | Cualquiera |
project_image | 1 | 5 MB | PNG, JPEG, WebP, GIF o SVG |
budget_attachment | 20 | 50 MB | Cualquiera |
marketing_image | 20 | 5 MB | PNG, JPEG, GIF o WebP |
disconnection_policy | 1 | 10 MB | PDF, JPEG, PNG, WebP o HEIC |
El tipo se comprueba dos veces: al reservar, con el que declaras, y al confirmar, con el del archivo que ha llegado.
Errores
Todos llegan con isError: true, el código en details.errorCode y una pista para el asistente en hint. En request_upload, confirm_upload y abort_upload, un archivo que falla no tumba la llamada: sale en rejected, failed o notReleased con su errorCode.
errorCode | Cuándo | Qué hacer |
|---|---|---|
UPLOAD_DESTINATION_UNKNOWN | destination.type no es ningún destino. | Usa uno de tu servidor: el mensaje los nombra. |
UPLOAD_DESTINATION_NOT_IN_SERVER | El destino es de otro servidor. | Llama a file_uploads del servidor que dice el mensaje. |
UPLOAD_DESTINATION_RECORD_REQUIRED | Falta destination.id en un destino con ficha, o sobra en uno sin ella. | Añádelo o quítalo. |
MODULE_NOT_ENABLED | El aplicativo del destino no está en el plan. | No es un fallo: díselo a la persona. Repetir la llamada da lo mismo. |
PERMISSION_DENIED | A la persona le falta un permiso del destino. Trae requiredPermissions. | Que se lo conceda quien administra la organización. |
INSUFFICIENT_SCOPE | A la conexión le falta el scope del destino. Trae requiredScopes y, en OAuth, grantUrl. | Dale el enlace a la persona; si es una clave personal, crea otra. |
UPLOAD_DESTINATION_NOT_FOUND | La ficha no existe o queda fuera de tu alcance. | Búscala con search y repite con su id. No digas que no existe: puede estar fuera de tu alcance. |
UPLOAD_DESTINATION_TYPE_NOT_ACCEPTED | El destino no admite ese tipo de archivo. | Pide el archivo en uno de los tipos que dice el mensaje. |
UPLOAD_DESTINATION_FILE_TOO_LARGE | El archivo pasa del tope del destino. Trae maxFileBytes. | Pide uno más ligero. |
UPLOAD_DESTINATION_TOO_MANY_FILES | Más archivos de los que admite el destino de una vez. | Repártelos en varias llamadas. |
TOO_MANY_UPLOAD_RESERVATIONS | Demasiadas reservas sin terminar. Trae retryAfterSeconds. | Confirma o libera con abort_upload las que tienes, y espera. |
UPLOAD_NOT_CONFIRMED | La operación del destino recibe un fileId sin confirmar. | Llama antes a confirm_upload y repite. |
UPLOAD_DESTINATION_MISMATCH | El archivo se subió para otro destino u otra ficha, o su contenido cambió después de confirmarlo; en ese caso se ha borrado. | Súbelo otra vez con el destino y la ficha de esa operación. |
UPLOAD_DESTINATION_EXPIRED | Han pasado las 24 horas. | Reserva otra subida con el mismo destino. |
UPLOAD_DESTINATION_IN_USE | El archivo ya se está usando o ya se ha usado. | No repitas a ciegas: mira cómo ha quedado la ficha. |
UPLOAD_DESTINATION_FILE_NOT_FOUND | No hay ninguna subida tuya con ese fileId para ese destino. | Mira tus subidas con list. |
UPLOAD_DESTINATION_UNAVAILABLE | No se puede comprobar el destino ahora mismo. No se ha reservado ni usado nada. | Pide a la persona que lo suba desde la aplicación. |
UPLOAD_DESTINATION_READ_BUSY | Hay otros archivos grandes leyéndose a la vez. Trae retryAfterSeconds. | Repite la misma llamada pasados esos segundos. |
UPLOAD_LINK_NOT_IN_DIRECTORY | create_upload_link desde un conector de un directorio. | Pide a la persona que lo suba desde la aplicación. |
Para subir a una carpeta del Gestor de archivos, a un lead, un cliente, un contacto o una oportunidad, las herramientas son fm_files_manage y crm_files_management del servidor utilia-crm, con las mismas dos salidas; lo explica Subir un archivo cuando el PUT no llega.