REST API

La REST API es una sola ruta de solo lectura sobre tres de las colecciones de Bidlo: projects, contractors y facilities. Usa la misma clave de API del equipo que el servidor MCP, que necesita una cuenta de Bidlo, y cualquiera puede conseguir una agendando una llamada. Esta página es todo: la solicitud, los parámetros, la respuesta y los errores.

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

Contenido

¿Qué cubre la REST API?

La REST API es una sola ruta de solo lectura sobre tres colecciones: projects, contractors y facilities. Se llama como GET or POST on https://lite.bidlo.ai/api/v1/{collection_id}, donde collection_id es una de las tres. Nada en esta API crea, edita ni borra: esos son los únicos dos métodos que responde la ruta.

ColecciónQué contiene
projectsObras desde el anuncio hasta la adjudicación: propietario, condado, fecha de licitación, valor.
contractorsCada empresa de un mercado: contratistas principales, subcontratistas, vendedores, proveedores.
facilitiesDónde operan esas empresas: plantas, bancos de material, patios, oficinas, con coordenadas.

La ruta también responde a sources, un alias obsoleto de facilities que se mantiene para quienes ya lo usan. Escribe facilities en trabajo nuevo. Cualquier otro nombre en esa posición es un 404, y esa revisión corre antes de leer la clave, así que una colección equivocada responde igual con clave o sin ella.

Todo lo demás que puede ver tu equipo, desde partidas hasta condados y los archivos de una obra, se lee con el servidor MCP. Mira Servidor MCP.

La forma legible por máquina de esta página es /openapi.json, un documento OpenAPI 3.1.

¿Cómo me autentico?

Manda la clave de API de tu equipo como token bearer en el encabezado Authorization, y envía un team_id en cada solicitud. La clave decide qué equipo se lee, así que el team_id que mandas nunca se compara con ella y una clave lee un solo equipo.

El encabezado en cada solicitud
Authorization: Bearer YOUR_API_KEY

Una clave empieza con sk_live_. Se necesita una cuenta de Bidlo. Cualquiera puede conseguir una agendando una llamada en https://cal.com/matt-wolfe-yecrho/30min — Bidlo prepara el equipo, y luego un dueño o administrador del equipo crea la clave de API en Settings → Features → API. No hay un plan público ni se puede abrir una cuenta por cuenta propia. La secuencia completa, el formato de la clave y qué hacer cuando una llamada regresa 401 o 403 están en Acceso y claves.

En un GET, team_id es un parámetro de consulta. En un POST es una clave en el cuerpo. Una solicitud sin él es un 400 que dice team_id is required, con clave o sin ella.

¿Cómo hago una solicitud?

Un GET lleva todos los parámetros en la cadena de consulta y un POST los lleva todos en un cuerpo JSON, y los dos aceptan los mismos parámetros. Un GET nunca lee el cuerpo y un POST nunca lee la cadena de consulta.

Los cuatro parámetros que son arreglos u objetos (fields, filters, sorts and grouping) son JSON. En un POST ya son JSON. En un GET cada uno es texto JSON, codificado en la URL dentro de la cadena de consulta. Un parámetro que la ruta no conoce se ignora.

ParámetroTypePredeterminadoLo que hace
team_idstringrequiredEl id de tu equipo. Obligatorio en cada solicitud. Una clave lleva su propio equipo, así que el valor que mandes no se compara con él.
fieldsarray of field idsevery fieldQué campos vienen. Déjalo fuera para traerlos todos.
filtersarraynoneQué documentos vienen.
sortsarraynewest firstEl orden en que vienen.
groupingobjectnonePide la respuesta en grupos en vez de una sola lista plana.
limitnumber100Filas por página. 1 a 2000.
pagenumber1Qué página, contando desde 1.
querystringnoneTexto libre, que se busca en toda la colección.
Obras que se licitan en una ventana de fechas, las más tempranas primero
curl -X POST https://lite.bidlo.ai/api/v1/projects \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "team_id": "<team_id>",
    "filters": [
      {
        "field_id": "<bid_date_field_id>",
        "operator": "between",
        "value": { "start": "2026-10-01", "end": "2026-12-31" }
      }
    ],
    "sorts": [{ "field_id": "<bid_date_field_id>", "direction": "asc" }],
    "limit": 100
  }'

La misma solicitud como GET. --data-urlencode hace la codificación, así que el JSON se puede escribir tal cual.

La misma solicitud como GET
curl --get https://lite.bidlo.ai/api/v1/projects \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data-urlencode "team_id=<team_id>" \
  --data-urlencode 'filters=[{"field_id":"<bid_date_field_id>","operator":"between","value":{"start":"2026-10-01","end":"2026-12-31"}}]' \
  --data-urlencode 'sorts=[{"field_id":"<bid_date_field_id>","direction":"asc"}]' \
  --data-urlencode "limit=100"

Algo que vigilar en un GET: un parámetro JSON que no se puede leer se descarta en vez de rechazarse. Un filters mal escrito devuelve 200 con la colección entera, así que lee meta y el conteo de filas antes de confiar en una respuesta amplia.

¿Cómo filtro, ordeno y pagino?

Los filtros y los órdenes son arreglos cuyas entradas nombran un campo por su id de campo, y una página es un límite y un número de página. El operador que acepta un campo y el formato de su valor siguen el tipo de ese campo, que Consultar datos detalla tipo por tipo.

filters

Un filtro
"filters": [
  {
    "field_id": "<field_id>",
    "operator": "<operator>",
    "value": "<shaped by the field type>",
    "conjunction": "AND"
  }
]

field_id and operator son obligatorios. value se omite para is_null and is_not_null y es obligatorio para todos los demás operadores. conjunction is AND a menos que escribas OR.

Cada campo acepta un solo filtro. Un segundo filtro sobre el mismo campo es un 400 que nombra los dos operadores, porque los filtros se identifican por id de campo y el segundo reemplazaría al primero sin avisar. Mejor combínalos: un filtro cuyo valor sea toda la lista de opciones permitidas, o un solo between para una ventana de fechas.

conjunction va en cada filtro y no hay anidación. Cada filtro marcado OR forma un grupo, y ese grupo se une con AND a los filtros marcados AND.

Un filtro cuyo valor está vacío (null, un arreglo vacío, una cadena en blanco, la mitad de un rango) se descarta en vez de rechazarse, y la respuesta regresa sin él.

Plantas, bancos de material y patios a 50 km de un punto
curl -X POST https://lite.bidlo.ai/api/v1/facilities \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "team_id": "<team_id>",
    "filters": [
      {
        "field_id": "<location_field_id>",
        "operator": "within_distance",
        "value": { "lat": 30.2672, "lng": -97.7431, "radiusInMeters": 50000 }
      }
    ],
    "limit": 100
  }'
Contratistas cuyo nombre contiene una palabra
curl -X POST https://lite.bidlo.ai/api/v1/contractors \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "team_id": "<team_id>",
    "filters": [
      { "field_id": "<name_field_id>", "operator": "contains", "value": "Paving" }
    ],
    "limit": 25
  }'

sorts

Un orden
"sorts": [
  { "field_id": "<field_id>", "direction": "asc" }
]

direction is asc or desc. Varios órdenes se aplican en el orden en que aparecen en el arreglo, y cada campo acepta un solo orden. Ordenar por un campo de ubicación también necesita el punto desde el que se mide, como un location en la entrada del orden. Sin ningún orden, los documentos más nuevos vienen primero.

limit and page

limit son filas por página. Su valor predeterminado es 100 y va de 1 a 2000; fuera de esos límites la solicitud es un 400 en vez de recortarse. page cuenta desde 1 y vuelve en meta.

No regresa un conteo total ni hay un enlace al siguiente. Una página más corta que el límite es la última, y un data vacío es el final.

La segunda página de las obras de un condado, las más nuevas primero
curl -X POST https://lite.bidlo.ai/api/v1/projects \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "team_id": "<team_id>",
    "filters": [
      { "field_id": "<county_field_id>", "operator": "contains", "value": ["<county_document_id>"] }
    ],
    "sorts": [{ "field_id": "<bid_date_field_id>", "direction": "desc" }],
    "limit": 100,
    "page": 2
  }'

grouping es el único parámetro avanzado aquí. Recibe un id de campo y el tipo de ese campo, el arreglo data vuelve como grupos y no como documentos, y page selecciona grupos y no filas. Casi nadie lo usa.

¿Cómo se ve una respuesta?

Una respuesta es un arreglo data de documentos y un objeto meta, y cada documento lleva sus valores en fields, identificados por id de campo. Un documento que nunca se ha editado no tiene updated_at, y la clave se omite en lugar de mandarse vacía.

Una respuesta
{
  "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>"
        }
      }
    }
  ],
  "meta": { "page": 1, "collection_id": "<collection_id>" }
}

field_name es el nombre que tu equipo le da a ese campo, que otro equipo puede haber cambiado. field_id es el mismo para los dos. Un id de campo que la colección no conoce vuelve con el nombre Unknown Field con tipo text en vez de lanzar un error.

Lo que va bajo value depende del tipo del campo.

Tipo de campoValueLo que es
dateUna cadenaISO 8601, o null cuando el valor guardado no se puede leer.
text, title, number, material, booleanTal como se guardaEl valor que guarda el campo, sin tocar.
tag{ id, value, document_id }Un objeto: la opción, o el único documento al que apunta una relación de un solo documento.
tags, relation[ { id, value, document_id } ]Un arreglo de esos objetos, uno por documento vinculado.
location{ id, name, address, lat, lng }El lugar, con sus coordenadas.
state{ id, name, abbreviation }El estado.
peopleIdsEsta ruta no los convierte en nombres.
formulaTal como se guardaLo que devuelva la fórmula, en esa forma.

Los ids dentro de un valor de tipo tag, relation o people son ids, no nombres. Lee el documento detrás de uno con la herramienta MCP query_mentioned_document, o hazlo al revés y convierte un nombre en un id con resolve_documents_by_name. Los dos están en Servidor MCP.

¿Qué errores puedo recibir?

Cada error regresa como JSON con una clave error, y el estado dice si el problema fue la colección, la clave, el equipo o la solicitud. Un 400 es la solicitud, un 401 o 403 es la clave o el equipo que hay detrás, un 404 es la colección de la ruta, y un 500 es la lectura misma.

StatusWhenLo que devuelve
400no se mandó team_idteam_id is required
400Un cuerpo POST que no es JSON válidoInvalid JSON body
400Una cadena de consulta que no se puede leerInvalid query parameters
400limit fuera de sus límites, u otro parámetro que la configuración rechazaInvalid view configuration, con un objeto details que nombra el parámetro
400Dos filtros en un mismo campoTwo filters target … Bidlo applies one filter per field, so the second would silently replace the first
400Un filtro sobre un id de campo que esta colección no tieneUnknown filter field … Check the id with get_collection_fields
400Un valor de filtro con el formato equivocadoFilter on … expects …, que nombra el campo, su tipo y el formato que esperaba
401Sin clave bearer en la solicitudUnauthorized
401Una clave que no es una clave activa de Bidlo, o una que ya se borróInvalid API key
403La suscripción del equipo vencióTeam subscription expired
404Una colección que la ruta no conoceInvalid collection: … Must be one of: projects, contractors, facilities, sources or a valid collection UUID
500La lectura misma fallóFailed to fetch view content, con el mensaje de origen en details

Un 400 de un filtro nombra el campo, su tipo y el formato que esperaba, así que lee el mensaje en vez de adivinar el valor. Un 400 en limit lleva un objeto details que nombra el parámetro que falló.

¿De dónde salen los ids de los campos?

Los ids de campo salen de la herramienta MCP get_collection_fields, o del field_id impreso junto a cada valor en una respuesta, porque hoy la REST API no tiene su propio listado de campos. Léelos una vez para tu equipo, guárdalos, y llévalos a tus filtros y a tus órdenes.

Los nombres no son ids. Un equipo puede renombrar un campo integrado, así que el mismo campo se lee con un nombre en una cuenta y con otro en otra, mientras el id se queda igual. Por eso en esta página no hay ningún id impreso, y por eso un filtro que nombra un campo con palabras va en el servidor MCP y no aquí.

Un campo que otro equipo agregó a la misma colección no es un campo tuyo. Filtrar por su id devuelve el 400 de campo desconocido, que nombra get_collection_fields como lo siguiente que hay que llamar.

Próximamente. Se están agregando dos rutas REST para esto: /api/v1/collections and /api/v1/collections/{collection_id}/fields. Ninguna está disponible hoy. Mientras tanto, lee los ids en MCP y llévalos al otro lado.

¿Puedo llamarla desde un navegador?

No, la ruta no manda encabezados CORS ni responde el preflight, así que llámala desde un servidor y no desde una página en el navegador. El navegador manda la solicitud y luego se niega a entregarle la respuesta a tu propio script.

La clave es la segunda razón. Todo lo que hay en una página lo puede leer cualquiera que la cargue, así que una clave en el código de la página es una clave regalada. Guárdala en tu servidor, deja que tu servidor llame a Bidlo, y manda a tu página solo lo que necesita.

Tampoco regresan encabezados de caché. Un cliente que quiera guardar una respuesta lo decide por su cuenta.

¿A dónde voy después?

Lee Consultar datos para los operadores y las formas de valor que acepta un filtro, y Servidor MCP para la herramienta que lista los ids de tus campos.

  • Consultar datos: los operadores, los formatos de valor y las palabras clave de fecha, tipo por tipo.
  • Servidor MCP: las cinco herramientas, incluida la que lista los ids de tus campos.
  • Acceso y claves: cómo conseguir una clave, y qué significa un 401 o un 403.
  • /openapi.json: la misma ruta como documento OpenAPI.

Lo que esta ruta no responda, escribe a support@bidlo.ai.