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:
| Prefix | Resource |
|---|---|
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}` } }
);