Skip to main content

Data Model and Tenancy

Tenancy

BCMLogic Next is multi-tenant by default. Every resource belongs to exactly one tenant. There is no cross-tenant query, view, or aggregation accessible through the public API.

The tenant is resolved from the bearer token. Clients do not pass tenant identifiers in URLs or request bodies — doing so has no effect. If your integration manages multiple tenants (for example, a managed service provider), authenticate separately per tenant.

See ADR-006 for the isolation model.

Resource Identifiers

All identifiers are prefixed ULIDs. The prefix identifies the resource type:

PrefixResource
rsk_Risk
vnd_Vendor
doc_Document
prc_Process (Continuity)
usr_User
gen_AI generation
rep_Report
evt_Audit event

Prefixes are informational. Always treat IDs as opaque strings — do not parse, truncate, or construct them.

Relationships

Resources reference each other by ID. Relationships are surfaced in a relationships block on each resource. Use the links.related pattern to navigate — avoid building URLs by string concatenation.

{
"id": "vnd_01HQ8K3MNPQRS",
"type": "vendor",
"attributes": {
"name": "Acme Cloud",
"status": "active"
},
"relationships": {
"contracts": {
"links": { "related": "/v1/core/vendors/vnd_01HQ8K3MNPQRS/contracts" }
},
"primary_contact": {
"data": { "id": "usr_01HQ8K3MNPQRT", "type": "user" }
}
}
}

Example — follow a relationship

const vendor = await fetchResource(token, '/v1/core/vendors/vnd_01HQ8K3MNPQRS');
const contractsUrl = vendor.relationships.contracts.links.related;
const contracts = await fetchResource(token, contractsUrl);

Versioning of Business Records

Mutable resources carry a meta.version counter. Every update increments the version and writes an audit event.

To update safely under concurrent writes, send If-Match: <version> on PATCH and PUT. The server returns 409 Conflict if the version has changed since you last read the resource.

# First, read the current version
GET /v1/core/vendors/vnd_01HQ8K3MNPQRS
# meta.version: 5

# Then update, guarding against concurrent modification
PATCH /v1/core/vendors/vnd_01HQ8K3MNPQRS
If-Match: 5
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440003

Audit Trail

Every mutation in Core API produces an immutable audit event. Audit events record:

  • Actor — which user or service account performed the action.
  • Action — the operation type (create, update, delete, etc.).
  • Resource — the affected resource type and ID.
  • Diff — before and after values for updates.
  • Timestamp — RFC 3339 in UTC.
  • Request ID — links to the originating HTTP request.

Query audit events through GET /v1/core/audit/events. The endpoint requires the audit:read scope on the bearer token — see Authentication → Scopes.

Audit events are append-only. They cannot be modified or deleted through the public API.

Example — read audit events for a vendor

curl "https://api.bcmlogic.com/v1/core/audit/events?resource_type=vendor&resource_id=vnd_01HQ8K3MNPQRS&sort=-created_at" \
-H "Authorization: Bearer eyJhbGc..."
const events = await fetch(
'https://api.bcmlogic.com/v1/core/audit/events' +
'?resource_type=vendor&resource_id=vnd_01HQ8K3MNPQRS&sort=-created_at',
{ headers: { 'Authorization': `Bearer ${token}` } }
);