REST API

La REST API lee y escribe los datos de Bidlo de tu equipo: cualquier colección que tu equipo pueda ver, por id o por nombre. Usa la misma clave de API que el servidor MCP. Esta página lo cubre todo: las operaciones, la solicitud, la respuesta, las escrituras, los límites y los errores.

Abrir en ChatGPTAbrir en Claudellms.txtllms-full.txtopenapi.json

Contenido

¿Qué cubre la REST API?

La API REST lee y escribe los documentos de cualquier colección que tu equipo pueda ver, en https://app.bidlo.ai/api/v1. Cada ruta de abajo parte de https://app.bidlo.ai. Las lecturas requieren una clave que pueda leer datos; las escrituras requieren una que pueda editar datos o colecciones y campos, como indica la tabla.

MétodoRutaLo que hacePermiso
GET/api/v1/meMuestra qué puede hacer esta clave: sus permisos, su límite de colecciones y su vencimiento.Read data
GET/api/v1/collectionsLista las colecciones que esta clave puede leer.Read data
POST/api/v1/collectionsCrea una colección para tu equipo.Editar colecciones y campos
GET/api/v1/collections/{collection_id}Lee una colección, por id o por nombre.Read data
PATCH/api/v1/collections/{collection_id}Renombra una colección o cambia su descripción o ícono.Editar colecciones y campos
DELETE/api/v1/collections/{collection_id}Elimina una colección de tu equipo u oculta una colección pública para tu equipo.Editar colecciones y campos
GET/api/v1/collections/{collection_id}/fieldsLista los campos de una colección: id, nombre y tipo.Read data
POST/api/v1/collections/{collection_id}/fieldsAgrega un campo, una fórmula o una relación.Editar colecciones y campos
GET/api/v1/collections/{collection_id}/documentsLee una página de documentos: filtros, órdenes, un límite y un cursor.Read data
POST/api/v1/collections/{collection_id}/documentsAgrega un documento, o hasta 500 a la vez.Edit data
PATCH/api/v1/collections/{collection_id}/documentsCambia campos de hasta 500 documentos de una colección.Edit data
POST/api/v1/collections/{collection_id}/documents/queryLa misma lectura, con la consulta como un solo objeto JSON.Read data
GET/api/v1/documents/{document_id}Lee un documento por id.Read data
PATCH/api/v1/documents/{document_id}Cambia campos de un documento.Edit data
DELETE/api/v1/documents/{document_id}Manda un documento a la papelera.Edit data
PUT/api/v1/documents/{document_id}/relations/{field_id}Reemplaza los documentos a los que apunta un campo de relación.Edit data
POST/api/v1/documents/{document_id}/restoreSaca un documento de la papelera.Edit data
POST/api/v1/collections/{collection_id}/documents/upsertBusca las filas por uno a tres campos, actualiza las coincidencias y agrega el resto. Puede correr primero como prueba.Edit data
GET/api/v1/documents/{document_id}/noteLee la nota de un documento como Markdown.Read data
PUT/api/v1/documents/{document_id}/noteReemplaza la nota de un documento, y se niega si alguien la guardó desde que la leíste.Edit data
POST/api/v1/documents/{document_id}/note/appendAgrega texto al final de una nota.Edit data
PATCH/api/v1/fields/{field_id}Renombra un campo o cambia sus ajustes o fórmula.Editar colecciones y campos
DELETE/api/v1/fields/{field_id}Elimina un campo; los campos primarios y del sistema se conservan.Editar colecciones y campos
GET/api/v1/fields/{field_id}/optionsLista las opciones de un campo de etiqueta.Read data
POST/api/v1/fields/{field_id}/optionsAgrega una opción de etiqueta, con un color opcional.Editar colecciones y campos
POST/api/v1/files/uploadsDevuelve un token y una URL firmados para cargar un archivo.Edit data
POST/api/v1/filesVerifica una carga, crea el archivo y lo pone en cola para procesarlo.Edit data
GET/api/v1/files/{file_id}Lee detalles del archivo y una URL de descarga válida por 15 minutos.Read data
DELETE/api/v1/files/{file_id}Envía un archivo del equipo a la papelera.Edit data
GET/api/v1/documents/{document_id}/commentsLee los comentarios de un documento.Read data
POST/api/v1/documents/{document_id}/commentsAgrega un comentario como la persona dueña de la clave.Edit data
PATCH/api/v1/comments/{comment_id}Edita un comentario de la persona dueña de la clave.Edit data
DELETE/api/v1/comments/{comment_id}Elimina un comentario de la persona dueña de la clave.Edit data
GET/api/v1/documents/{document_id}/historyLee el historial de campos de un documento.Read data

{collection} y {field} aceptan un id o un nombre, y {document_id} siempre es un id; Consultar datos explica cómo se comparan los nombres. Ninguna clave tiene acceso a las colecciones internas de la app (páginas, chats, flujos, vistas y similares), y los webhooks y los ajustes del equipo no tienen ruta.

/openapi.json es toda la API como especificación OpenAPI 3.1; la app ofrece la suya en https://app.bidlo.ai/api/v1/openapi.json, sin necesidad de clave.

¿Cómo me autentico?

Manda tu clave de API como token bearer en el encabezado Authorization; la clave lleva tu equipo, así que ninguna solicitud manda un team_id. La misma clave funciona en el servidor MCP.

El encabezado en cada solicitud
Authorization: Bearer YOUR_API_KEY

Una clave empieza con sk_live_. Acceso y claves explica cómo conseguir una, qué puede hacer y cómo revisarla con GET /me.

¿Cómo hago una solicitud?

Un GET lleva su entrada en la cadena de consulta y cualquier otro método lleva un cuerpo JSON, y los dos rechazan un parámetro que no conocen. Un team_id, o cualquier otro parámetro que la operación no acepta, es un 422.

En un GET, fields, filters, sorts, grouping, page, limit son texto JSON, codificado en la URL dentro de la cadena de consulta. Uno que no se pueda leer es un 422 que dice Invalid filters (o el que haya sido), no una respuesta completa sin aviso. Una consulta larga se escribe más fácil como POST …/documents/query, que acepta la misma entrada como cuerpo JSON.

Gana la ruta. Un cuerpo que repite un valor de la ruta con otro distinto es un 422 que dice Conflicting collection_id.

Una respuesta lleva el encabezado X-Request-Id en cuanto empieza la operación. Un error anterior a eso (una ruta desconocida, un método equivocado, un JSON que no se puede leer, un valor de ruta en conflicto) lleva su request_id solo en el cuerpo. Cítalo cuando escribas a soporte.

¿Cómo filtro, ordeno y pagino?

Los filtros y los órdenes nombran un campo por id o por nombre, y paginas con un límite de hasta 500 y el cursor que devuelve cada página llena. Consultar datos detalla los operadores, el valor que acepta cada tipo de campo, los órdenes, la paginación y la agrupación.

ParámetroTipoPredeterminadoLo que hace
fieldsarray of field ids or namesevery fieldQué campos regresan. Los campos por los que ordenas también pueden regresar.
filtersarraynoneQué documentos regresan. Hasta 100.
sortsarraynewest firstEl orden en que regresan. Hasta 100.
groupingobjectnonePide la respuesta en grupos en vez de una sola lista plana.
querystringnoneTexto libre, que se busca en los campos que pides y en el título.
limitnumber100Filas por página. De 1 a 500.
pagenumber1Qué página, contando desde 1.
cursorstringnoneDónde terminó la última página: su meta.next_cursor.
Obras que se licitan en un rango de fechas, la más próxima primero (Bid Date es un campo de ejemplo)
curl -X POST https://app.bidlo.ai/api/v1/collections/<collection>/documents/query \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": [
      {
        "field_id": "Bid Date",
        "operator": "between",
        "value": { "start": "2026-10-01", "end": "2026-12-31" }
      }
    ],
    "sorts": [{ "field_id": "Bid Date", "direction": "asc" }],
    "limit": 100
  }'
La misma lectura como GET (Bid Date es un campo de ejemplo)
curl --get https://app.bidlo.ai/api/v1/collections/<collection>/documents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data-urlencode 'filters=[{"field_id":"Bid Date","operator":"between","value":{"start":"2026-10-01","end":"2026-12-31"}}]' \
  --data-urlencode 'sorts=[{"field_id":"Bid Date","direction":"asc"}]' \
  --data-urlencode "limit=100"

¿Cómo se ve una respuesta?

Cada operación exitosa devuelve su resultado en data, con la paginación en meta cuando devuelve una página. GET /documents/{document_id} devuelve un documento con la misma forma, en data.

Una respuesta (Bid Date es un campo de ejemplo)
{
  "data": [
    {
      "id": "<document_id>",
      "name": "<the value of the collection's primary field>",
      "collection_id": "<collection_id>",
      "created_at": "<iso timestamp>",
      "updated_at": "<iso timestamp>",
      "fields": {
        "<field_id>": {
          "value": "<shaped by field_type>",
          "field_name": "Bid Date",
          "field_type": "date",
          "field_id": "<field_id>",
          "field_value_id": "<field_value_id>"
        }
      }
    }
  ],
  "meta": {
    "page": 1,
    "limit": 100,
    "collection_id": "<collection_id>",
    "next_cursor": "<a string, or null on the last page>"
  }
}

field_name es el nombre que tu equipo le dio al campo, y field_id no cambia cuando cambia el nombre. Lo que está en value sigue el tipo de campo; Consultar datos muestra cada formato.

¿Cómo agrego y cambio documentos?

Con una clave que pueda editar datos, haz POST de documentos a una colección para agregarlos, PATCH a un documento para cambiarlo, y upsert para comparar filas con lo que ya existe. Las escrituras cambian los documentos de tu equipo en Bidlo, igual que una edición en la app hecha por la persona que creó la clave.

Crear

documents es un objeto { name, fields } o un arreglo de hasta 500. name es el título. Bidlo crea los ids. data contiene un documento completo para una entrada, o un arreglo para un lote. Su id es el id del documento, con el mismo formato que una lectura de documento. El valor predeterminado de un campo no se llena en un documento creado mediante la API.

Agregar una obra (Bid Date y County son campos de ejemplo)
curl -X POST https://app.bidlo.ai/api/v1/collections/<collection>/documents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: <a new unique string for each write>" \
  -d '{
    "documents": [
      {
        "name": "<title>",
        "fields": {
          "Bid Date": "2026-11-12",
          "County": ["<document_id>"]
        }
      }
    ]
  }'

Actualizar

PATCH /documents/{document_id} acepta fields y cambia solo los campos que mandas.

Mover una fecha de licitación (un campo de ejemplo)
curl -X PATCH https://app.bidlo.ai/api/v1/documents/<document_id> \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "fields": { "Bid Date": "2026-11-19" } }'

Actualización masiva

PATCH /collections/{collection}/documents acepta hasta 500 filas { document_id, fields } de esa colección y responde con data.updated_count.

Cambiar varios documentos a la vez
curl -X PATCH https://app.bidlo.ai/api/v1/collections/<collection>/documents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "documents": [
      { "document_id": "<document_id>", "fields": { "<field_id>": "<new value>" } },
      { "document_id": "<document_id>", "fields": { "<field_id>": "<new value>" } }
    ]
  }'

Upsert

match_fields nombra de uno a tres campos que juntos identifican un documento. Cada una de las hasta 500 filas en rows que coincide con un documento lo actualiza, y cada una que no coincide con ninguno se agrega. update_mode es overwrite por defecto, o fill_empty para llenar solo los campos vacíos. En data, la respuesta cuenta lo que se creó y actualizó, y un arreglo errors nombra cualquier fila que coincidió con más de un documento (ambiguous_match), y esa fila se omite.

Usa campos distintos de tipo title, text, number, date, state o tag, o relaciones directas de un solo destino. La comparación recorta y agrupa espacios, ignora mayúsculas y minúsculas y estandariza fechas. Las etiquetas y relaciones coinciden por nombre, no por id. Una clave de coincidencia totalmente vacía nunca coincide.

Un documento público coincidente con un nombre distinto se omite con public_document_rename, incluso con fill_empty; actualiza su campo principal por separado. Envía cada clave de coincidencia una sola vez por lote.

Upsert también acepta etiquetas para tags y relaciones. Resuelve las etiquetas existentes y crea los documentos referenciados que falten en una ejecución real; una prueba no crea ninguno.

dry_run: true reporta lo que pasaría y no escribe nada. Córrelo primero en una importación grande.

Probar una importación sin escribir (Name, Bid Date y Status son campos de ejemplo)
curl -X POST https://app.bidlo.ai/api/v1/collections/<collection>/documents/upsert \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "match_fields": ["Name", "Bid Date"],
    "rows": [
      { "name": "<title>", "fields": { "Bid Date": "2026-11-12", "Status": "<document_id>" } }
    ],
    "update_mode": "fill_empty",
    "dry_run": true
  }'

Relaciones

PUT /documents/{document_id}/relations/{field} reemplaza todos los documentos a los que apunta un campo de relación con target_document_ids, hasta 500. Un arreglo vacío la deja en blanco. Una relación que acepta un solo documento rechaza más.

Apuntar una relación a dos documentos
curl -X PUT https://app.bidlo.ai/api/v1/documents/<document_id>/relations/<field_id> \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "target_document_ids": ["<document_id>", "<document_id>"] }'

Papelera y restauración

DELETE manda un documento a la papelera; no se borra nada. POST …/restore trae de vuelta la entrada más reciente de la papelera que puedes ver, o responde 404 que dice Document is not in your trash.

Mandar un documento a la papelera y luego restaurarlo
curl -X DELETE https://app.bidlo.ai/api/v1/documents/<document_id> \
  -H "Authorization: Bearer YOUR_API_KEY"

curl -X POST https://app.bidlo.ai/api/v1/documents/<document_id>/restore \
  -H "Authorization: Bearer YOUR_API_KEY"

Notes

La nota de un documento se lee como Markdown con GET …/note. PUT …/note la reemplaza y acepta base_updated_at: el data.updated_at de tu última lectura, o null para un documento sin nota. Si alguien guardó la nota desde entonces, la respuesta es un 409 conflict; vuelve a leerla y rehaz el cambio. POST …/note/append no necesita un token del cliente. Comprueba las ediciones simultáneas hasta tres veces y aun así puede devolver 409 conflict. Recorta los espacios del texto añadido y lo separa del contenido existente con dos saltos de línea. Una nota que incluye una vista no se puede cambiar mediante la API.

Reemplazar una nota
curl -X PUT https://app.bidlo.ai/api/v1/documents/<document_id>/note \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "markdown": "<the whole note, as Markdown>",
    "base_updated_at": "<updated_at from your last read, or null>"
  }'

¿Cómo cambio colecciones y campos?

Editar colecciones y campos permite que una clave cree, renombre y elimine colecciones y campos, incluidas fórmulas, relaciones y opciones de etiquetas. El permiso es schema:write. Elige Acceso completo o agrega este permiso en Personalizado; Leer y escribir datos no lo incluye.

Colecciones

POST /collections acepta un nombre y, opcionalmente, una descripción y un ícono. PATCH /collections/{collection_id} cambia esos detalles. DELETE /collections/{collection_id} elimina una colección del equipo u oculta una colección pública para tu equipo. El valor de data.status es deleted o soft_deleted. Al eliminar una colección propia se eliminan sus documentos y valores, las páginas dependientes y los campos del equipo que la referencian. Una clave limitada a ciertas colecciones no puede crear una colección.

Campos y fórmulas

POST /collections/{collection_id}/fields acepta name y type. Los tipos son text, number, tag, tags, relation, date, people, boolean, location, state, formula. PATCH /fields/{field_id} cambia un nombre, los ajustes, una fórmula o los ajustes de una relación. El tipo de un campo existente no puede cambiar. DELETE /fields/{field_id} elimina un campo; los campos primarios y del sistema no se pueden eliminar.

Las fórmulas usan ids de campos entre llaves, nunca nombres de campos. Los valores relacionados usan el id de un campo de relación seguido del id del campo de destino. La clave debe poder leer todas las dependencias.

Añadir un campo de fórmula
POST /collections/<collection>/fields
{
  "name": "Bid Total",
  "type": "formula",
  "formula": "{<field id>} * 2",
  "settings": { "decimal_places": 2 }
}
Una fórmula mediante una relación
{<relation field id>}.{<target field id>}

Una relación necesita reference_collection_id. Su objeto opcional relation acepta enable_two_way y reverse_field_name. Los campos vinculados deben pertenecer a tu equipo para cambiar el vínculo. Los destinos y los campos vinculados deben estar dentro del acceso de la clave.

settings acepta solo los ajustes indicados en la solicitud de OpenAPI. No acepta propiedades internas del campo ni tipos de resultado controlados por el servidor. Las fórmulas o los ajustes inválidos devuelven 422 validation.

Opciones de etiquetas

GET /fields/{field_id}/options lista opciones con Leer datos. POST /fields/{field_id}/options agrega una con Editar colecciones y campos. Envía name y, opcionalmente, color: un nombre válido de la paleta o un color hexadecimal, solo en una colección de opciones de color. En otro caso, el color devuelve un 422. Renombra o elimina una opción mediante las rutas de documentos, usando su id de documento.

Un campo de etiqueta puede crear su propia colección oculta de opciones. Una clave limitada a ciertas colecciones debe indicar una colección de opciones existente y permitida en reference_collection_id.

¿Cómo cargo un archivo?

Pide a la API las instrucciones de carga, envía los bytes con PUT directamente a la URL firmada de almacenamiento y finaliza la carga mediante la API. Cargar y finalizar requieren Editar datos. El límite por archivo es 250 MiB (262,144,000 bytes); el cuerpo de una solicitud a la API tiene un límite de 4.5 MB, por eso los bytes van directamente al almacenamiento.

Pedir instrucciones de carga
POST /files/uploads
{
  "filename": "plans.pdf",
  "content_type": "application/pdf",
  "size_bytes": 1024
}
Instrucciones de carga en data
{
  "data": {
    "upload_id": "<signed upload token>",
    "url": "<presigned S3 URL>",
    "method": "PUT",
    "headers": {
      "Content-Type": "application/pdf",
      "Content-Length": "1024",
      "If-None-Match": "*"
    },
    "expires_at": "<ISO timestamp>"
  }
}

El ejemplo declara un PDF de 1024 bytes; envía el tamaño y el tipo de contenido reales de tu archivo. Las instrucciones de carga vencen a los 15 minutos. Copia todos los encabezados recibidos al PUT, incluidos Content-Length y If-None-Match. Envía solo los bytes del archivo a la URL recibida.

Enviar los bytes del archivo al almacenamiento con PUT
curl -X PUT '<data.url>' \
  -H 'Content-Type: application/pdf' \
  -H 'Content-Length: 1024' \
  -H 'If-None-Match: *' \
  --data-binary @plans.pdf
Finalizar con la misma clave antes del vencimiento del token
POST /files
{
  "upload_id": "<data.upload_id>",
  "attach_to": { "document_id": "<document>", "field_id": "<field>" }
}

Finalizar verifica el tamaño y el tipo de contenido cargados, crea el archivo y lo pone en cola para procesarlo si el tipo es compatible. Reutilizar un valor válido de upload_id devuelve el mismo archivo activo. attach_to es opcional y reemplaza el valor del campo de destino; no lo agrega al final. El campo debe permitir escritura y apuntar a Files. Una clave limitada debe permitir Files y la colección donde se adjunta.

Detalles del archivo al finalizar o con GET
{
  "data": {
    "id": "<file>",
    "name": "plans.pdf",
    "filename": "plans.pdf",
    "content_type": "application/pdf",
    "size_bytes": 1024,
    "download_url": "<signed download URL>",
    "download_expires_at": "<ISO timestamp>"
  }
}

GET /files/{file_id} necesita Leer datos y devuelve una URL de descarga válida por 15 minutos. Solo los archivos activos del equipo que estén en su almacenamiento se pueden descargar así. DELETE /files/{file_id} necesita Editar datos y envía el archivo a la papelera. Las escrituras genéricas de documentos en Files siguen bloqueadas.

503 upload_signing_unavailable significa que la firma de cargas no está configurada en el servidor. Un token de carga inválido o vencido, o bytes que no coinciden con el tamaño o tipo declarados, devuelven 422 validation.

¿Cómo leo comentarios e historial?

Lee los comentarios o el historial de campos de un documento con su id, un límite y el cursor de la página anterior. Ambas lecturas requieren Leer datos. El parámetro limit tiene un valor predeterminado de 100 y acepta de 1 a 500. Los resultados están en data; envía meta.next_cursor de vuelta como cursor hasta que sea null.

Comments

GET /documents/{document_id}/comments lista comentarios. POST /documents/{document_id}/comments crea uno. Envía el comentario como texto en body. PATCH /comments/{comment_id} edita ese texto; DELETE /comments/{comment_id} elimina el comentario. Las escrituras requieren Editar datos. La persona dueña de la clave es la autora, y la clave solo puede editar o eliminar los comentarios de esa persona.

Agregar un comentario
POST /documents/<document>/comments
{ "body": "<comment text>" }
Una página de comentarios
{
  "data": [{
    "id": "<comment>",
    "user_id": "<user>",
    "author": null,
    "body": "<comment text>",
    "parent_id": null,
    "created_at": "<ISO timestamp>",
    "updated_at": null
  }],
  "meta": { "next_cursor": null }
}

Historial de campos

GET /documents/{document_id}/history devuelve los cambios de campos que la clave puede leer. old_value puede ser null cuando la app no lo guardó. Se omiten los campos y valores vinculados fuera del acceso de la clave. Una página puede estar vacía y aun tener un cursor siguiente; sigue consultando hasta que el cursor sea null.

Leer el historial de campos
GET /documents/<document>/history?limit=100&cursor=<next cursor>

Los elementos van del más reciente al más antiguo según created_at, luego id. Cada uno incluye id, created_at, action_type, field_id, new_value, old_value, source_url, user, field y meta.

field incluye id, name y type. user es null o incluye id, first_name, last_name y avatar; los últimos tres pueden ser null. source_url puede ser null; meta siempre es null.

¿Qué valor acepta cada campo?

Escribe cada valor en su forma simple: el texto como cadena, un número como número, una fecha en ISO 8601, y una etiqueta, varias etiquetas o una relación como ids de documento. En una creación o un upsert, el título va en name, nunca en fields. En una actualización, el título es un campo como cualquier otro: manda el id o el nombre del campo principal dentro de fields.

Tipo de campoEscríbelo como
text, titleUna cadena
numberUn número
booleantrue o false
dateUna fecha o marca de tiempo ISO 8601
tagUn id de documento
tags, relationUn arreglo de ids de documento
peopleUn arreglo de ids de usuario
locationUn Point, LineString o Polygon de GeoJSON. Las líneas y los polígonos se guardan como un Point representativo; su geometría no se conserva.
stateEl nombre o el código del estado

Omitir un campo lo deja sin cambios. Envía null o una cadena vacía para vaciar un campo escalar; envía una selección vacía para quitar tags o people, y [] para vaciar una relación.

Los campos de fórmula, rollup y lookup son de solo lectura, igual que un campo que agregó una integración y que mantiene de solo lectura. Un campo existente al que se conectó una integración sigue siendo editable. Escribir un campo de solo lectura es un 422 que nombra el campo. Lo mismo pasa al mandar el mismo campo dos veces en una fila. Un id de documento que no existe, está en la papelera o está en una colección que tu equipo no puede ver es un 404 que lista esos ids en details. Uno en una colección fuera del límite de la clave, o en una que la API no abre, es un 403.

¿Una escritura masiva es todo o nada?

No: cada fila se revisa antes de escribir nada, pero la escritura no es una sola transacción, así que una falla a medio camino puede dejar escritas las filas anteriores. La mayoría de los errores en un lote se atrapan en esa primera revisión, y no se escribe nada.

Cuando un lote sí falla a medio camino, lee antes de volver a escribir: consulta los documentos que mandaste, o vuelve a correr el lote como upsert, que actualiza lo que ya está en vez de agregarlo dos veces. Un Idempotency-Key no cubre esto: repite una respuesta que se guardó, y una solicitud que falló a medio camino no guardó ninguna, así que un reintento simple vuelve a escribir.

¿Qué errores puedo recibir?

Todo error es JSON con un objeto error que lleva un código y un mensaje, y un request_id para citar a soporte. Algunos errores agregan details: el campo, los ids o el parámetro que falló.

Un error
{
  "error": {
    "code": "validation",
    "message": "Unknown field",
    "details": { "field": "<the field you sent>" }
  },
  "request_id": "<request_id>"
}
StatusCodeCuándo
401unauthorizedSin clave, una clave que no es válida, o una clave cuyo creador salió del equipo.
401key_expiredLa clave pasó su fecha de vencimiento.
403subscription_expiredLa suscripción del equipo venció.
403forbidden_scopeA la clave le falta el permiso, la colección está fuera del límite de la clave, o la colección es una que la API no abre.
403forbidden_roleLa clave tiene el permiso, pero el rol de su creador ya no lo permite.
404not_foundNo existe esa ruta, colección o documento. Un 404 de documento lista los ids en details.
405method_not_allowedLa ruta existe, pero no con ese método. El encabezado Allow lista los que acepta.
409conflictAlguien guardó la nota después de que la leíste.
409idempotency_in_progressLa primera solicitud con este Idempotency-Key sigue corriendo.
409idempotency_unrecordedLa escritura se hizo, pero su respuesta no se pudo guardar. Lee antes de reintentar.
422validationLa entrada no cuadra: un parámetro desconocido, un valor equivocado, un campo desconocido o ambiguo, un campo de solo lectura, un cursor inválido.
422idempotency_mismatchEl Idempotency-Key ya se usó para otra solicitud.
429rate_limitedPor encima de las lecturas o escrituras del minuto. Retry-After dice cuántos segundos esperar.
500internal_errorLa llamada falló del lado de Bidlo.
503upload_signing_unavailableLa firma para cargar archivos no está configurada en el servidor.
503unavailableNo se pudo llegar a algo de lo que depende la llamada. Vuelve a intentar en un rato.

El encabezado X-Request-Id de la respuesta, cuando viene, coincide con request_id. Decide según code, no según message: el mensaje está escrito para personas y puede cambiar de redacción.

¿Cuántas llamadas puedo hacer?

Cada clave tiene 600 lecturas y 120 escrituras por minuto de reloj, y una llamada que supera ese límite es un 429 con un encabezado Retry-After. Dos claves no comparten un conteo.

Una respuesta que cuenta para el límite lleva X-RateLimit-Limit y X-RateLimit-Remaining. Si pasas el límite, espera los Retry-After segundos, que llegan hasta el inicio del siguiente minuto. Servidor MCP explica el límite de llamadas a herramientas.

¿Cómo reintento una escritura sin riesgo?

Manda un encabezado Idempotency-Key con la escritura, y un reintento con la misma clave y la misma solicitud recibe la primera respuesta durante 24 horas, siempre que esa respuesta se haya guardado. Funciona con cualquier método menos GET.

El encabezado
Idempotency-Key: <a new unique string for each write>

La clave tiene de 1 a 200 caracteres, y es nueva para cada escritura. La misma clave con una solicitud distinta es un 422 idempotency_mismatch, y un reintento mientras la primera sigue en curso es un 409 idempotency_in_progress. Una solicitud que falló no se guarda, así que un reintento vuelve a ejecutarla; una que nunca terminó queda libre después de diez minutos.

¿Y las rutas de colección anteriores?

Las rutas anteriores de projects, contractors y facilities siguen respondiendo con su contrato anterior, pero el código nuevo debe usar las rutas de documentos de colecciones. Son /api/v1/projects, /api/v1/contractors y /api/v1/facilities, y sources es un alias obsoleto de facilities. Cada solicitud debe enviar un team_id, limit llega hasta 2000 con page pero no cursor, y la mayoría de los errores regresan como { "error": "<message>" }.