Skip to main content

Read and synchronize with the API

Use the API to read Boldo from a script or another system. A synchronization must handle pagination, relationships, archives and permissions; a single asset list is not a complete repository export.

Make your first read​

  1. Create a key for the intended organization with the required read permissions.
  2. Use https://app.boldo.io/api/v1 in production, or your Boldo environment's origin followed by /api/v1.
  3. Send Authorization: Bearer <API_KEY> on each request. The key selects the organization; do not add an organization slug to this base URL.
  4. Read GET /metamodel/asset-types and GET /metamodel/relationship-types to obtain the actual type and property keys. Each relationship type names its source and target, and lists its own properties in properties, in the same form as an asset type's. filter[asset_type_key]={key} keeps the relationship types of one asset type; filter[source_asset_type_key] and filter[target_asset_type_key] narrow on one side.
  5. Request a small asset page and check its identifiers and visible fields.

Add Content-Type: application/json when sending JSON. Keep the credential out of URLs and logs. Type keys such as application in these examples are illustrative; replace them with your metamodel's keys.

GET /api/v1/assets?page[number]=1&page[size]=50&filter[asset_type_key]=application&include=total&sort=name
Authorization: Bearer <API_KEY>

Let your HTTP library encode query parameters. With curl, use --globoff or --get --data-urlencode because the brackets are part of Boldo's parameter names.

Paginate, filter and format​

List responses contain data and meta, including page_number, page_size and has_more. Start at page 1, then increase page[number] until has_more is false. The default page size is 50; the maximum is 100. include=total requests the optional total for assets and relationships.

Page numbers and sizes must be positive integers within the endpoint’s limits. Asset lists and searches reject unknown asset-type keys and property keys used in filters or sorting with HTTP 400. Read the metamodel instead of guessing them.

Filters on asset properties and system attributes also return HTTP 400 when a value is incompatible with its operator. For example, greater_than requires a valid number or date. A list is rejected when the operator expects a single value.

GET equality filters use filter[field]=value. Property fields use properties.<key>. Sort fields are comma-separated; prefix a field with - for descending order.

For grouped AND/OR conditions and comparison operators, use POST /search/assets or POST /search/relationships. These POST operations read data. Their filter-group objects reject extra JSON members; follow the documented structure exactly. Use the operator names from the API reference, such as greater_than_or_equals, rather than inventing shorthand names.

System timestamps use UTC ISO 8601. Date properties use YYYY-MM-DD. Rich text is returned as Markdown by default; request rich_text_format=plain for plain text. Interpret absent properties using the metamodel and permissions: absence does not establish an empty stored value.

Read relationships and archives​

Add include=relationships to include an asset's relationships. Add relationships.related_asset to expand neighbors, or relationships.related_asset.properties for their properties. This expansion has its own pagination through relationships_page[number] and relationships_page[size]. Use relationships_filter[direction], relationships_filter[relationship_type_key] and relationships_sort to narrow it.

Each parallel relationship has its own ID. Read /relationships or /relationships/{id} for relationship properties and updated_at. Filter that list with source_asset_id, target_asset_id or asset_id. include=source_asset,target_asset expands endpoints; append .properties to an endpoint include for its properties.

By default, lists return active data. For assets, archived=true selects archives and archived=any includes both states. An active asset has archived_at: null. For relationships, archived=true selects independently archived relationships whose endpoints remain active. archived=any also includes relationships archived with an asset.

Address an asset by UUID or by type:unique_property:value. The property must be a unique-key property, not simply a name with no current duplicates. URL-encode the identifier and choose the appropriate archive option when reading archived data.

A unique key reused after archiving can match both an active asset and an archive. A composite lookup including archives then returns a conflict; use the UUID to identify the intended asset. Expanded archived endpoints may provide their identity without property values.

Design incremental synchronization​

For example, send this body to POST /search/assets to read content changes from a known timestamp:

{
"archived": "any",
"filter": {
"logic": "and",
"conditions": [{
"field": "updated_at",
"operator": "greater_than_or_equals",
"value": "2026-09-01T00:00:00.000Z"
}]
},
"sort": [{ "field": "updated_at", "order": "asc" }],
"page": { "number": 1, "size": 100 },
"include": ["total"]
}

Process every page and deduplicate by ID. Save your checkpoint only after processing succeeds. Read an overlapping time interval on the next run to handle equal timestamps and concurrent changes. Pagination is not a frozen snapshot or a synchronization cursor.

Process archives using archived_at where required. Run a separate relationship pass with archived: "any" and updated_at; an asset-property pass alone does not describe relationship changes.

Content timestamps are not a complete change feed

Ownership and quality metadata need separate reconciliation. In particular, attestation and a request for reattestation do not update the asset's content timestamp. Inspect owned_by_ids, last_attested_at, last_attested_by_id, stale_marked_at, stale_marked_by_id, stale_mark_reason and completion_score as needed. These output fields are not all available as search filters; do not assume a returned field accepts a matching filter. Schedule dedicated reads or periodic full reconciliation. The API does not provide a tombstone feed for permanent deletions. A permission change can also make previously visible data disappear.

Select a scenario​

Read GET /scenarios, select an accessible scenario with status: "active" and ready: true, then add ?scenario=<ID> to each compatible request. If there are already parameters, add it with &. Omitting it selects real data. Do not send the internal x-boldo-scenario header: the public API rejects it. See scenario writes and restrictions.

Handle errors and rate limits​

The limit is 300 requests per 60 seconds per organization, shared by its API keys. At HTTP 429, honor Retry-After and retryAfterSeconds; additional keys do not multiply the limit.

HTTP statusWhat to do
400Correct the JSON or arguments using error, message and available details.
401Check that the key is present and valid.
403Check permissions, active membership, plan, allowed IPs and scenario eligibility.
404Check the identifier and visibility; this does not prove the resource does not exist.
409Read the current state and resolve the reported conflict before repeating a write.
429Wait for the returned delay and reduce the shared request rate.
500 or 503Retain the error and response header x-request-id for diagnosis; retry reads with a controlled delay.

After a write times out, read the state before retrying. A lost response does not prove that the write failed.