BCF stays the coordination standard. Nostr does not replace the BCF data model; it adds signed identity, append-only history, relay transport and project-wide discovery.
Contents
Scope and design rules
The mapping targets BCF 3.0 first, with a BCF 2.1 import/export fallback. It is intentionally conservative: fields from BCF are preserved, unknown extension data is kept in JSON, and event relationships are expressed with Nostr's standard indexed tags where possible.
Do not embed the model
BCF points to IFC elements through GUIDs. IFC files are separate file events.
Keep BCF GUIDs
BCF topic, comment and viewpoint GUIDs are preserved as explicit fields.
Current state + audit
The current topic is addressable. Every change is also an immutable audit event.
Viewpoints are immutable
If camera or selection changes, create a new viewpoint GUID and link it.
Nostr event IDs are derived from the signed event payload. That means the
BCF GUID must not be treated as the event ID. The BCF GUID lives in
d and bcf-guid; the Nostr id is the
signature-derived hash.
Event family
| Kind | Name | Nostr class | BCF object | Authority |
|---|---|---|---|---|
30902 | BCF project | addressable | Project + extensions | latest by d |
30900 | BCF topic | addressable | Topic / markup | latest by d |
30901 | BCF viewpoint | addressable, no republish | VisualizationInfo | one event per viewpoint GUID |
1170 | BCF comment | regular | Comment | append-only |
1171 | BCF audit | regular | Topic/comment event log | append-only |
1063 | File metadata | regular | Snapshot, IFC, document | hash-bound file record |
30903 | Document reference | addressable | DocumentReference | latest by d |
30904 | IFC file reference | addressable | BCF file/model ref | latest by d |
kind:30901: Nostr treats
30000-39999 as addressable replaceable events. BCF treats viewpoints as
immutable. This profile therefore forbids publishing a second
kind:30901 with the same d. A changed camera,
selection, clipping plane or snapshot requires a new BCF viewpoint GUID.
Tag vocabulary
Use indexed single-letter tags for discovery, and verbose BCF tags for
readability. Relays are expected to index single-letter tags such as
a, e, p, d,
s and t. Multi-letter tags are preserved but
should be filtered client-side unless the relay explicitly indexes them.
| Tag | Required on | Meaning | Example |
|---|---|---|---|
d | 30900, 30901, 30902, 30903, 30904 | Addressable identity, normally BCF GUID | ["d","b345...8228"] |
a | all project events | Project address | ["a","30902:<owner>:pilot-2026"] |
e | 1170, 1171, links | Referenced event ID with optional marker | ["e","<topic-id>","","root"] |
p | when people are involved | Participant, assignee, watcher or mentioned actor | ["p","<pubkey>","","assignee"] |
s | 30900 | Indexed topic status mirror | ["s","Open"] |
t | optional | Label, discipline, search facet | ["t","mep"] |
bcf-guid | BCF events | Original BCF GUID | ["bcf-guid","b345...8228"] |
bcf-version | BCF events | BCF semantic version | ["bcf-version","3.0"] |
bcf-status | 30900 | Project-extension status value | ["bcf-status","Open"] |
bcf-type | 30900 | Project-extension topic type | ["bcf-type","Clash"] |
bcf-priority | optional | Project-extension priority | ["bcf-priority","High"] |
bcf-stage | optional | Planning / project stage | ["bcf-stage","LP4"] |
bcf-due | optional | Due date as Unix timestamp | ["bcf-due","1779577200"] |
ifc | topic/viewpoint | IFC GlobalId / compressed GUID | ["ifc","2Y$0aB..."] |
ifc-file | topic/viewpoint | Linked kind:30904 file reference | ["ifc-file","<event-id>"] |
x | 1063 | SHA-256 file hash | ["x","<sha256>"] |
m | 1063 | MIME type | ["m","image/png"] |
BCF topic mapping
A BCF topic is the current issue state. It is addressable because BCF
topics change: status, priority, assignee, due date and labels may be
updated. The authoritative current state is the latest kind:30900
for the same d tag. For every meaningful update, emit a
matching kind:1171 audit event.
| BCF topic field | Nostr location | Rule |
|---|---|---|
guid | d, bcf-guid, content.guid | Preserve exactly. |
title | content.title, optional title tag | Human-readable issue headline. |
description | content.description | Markdown allowed in UI, plain string for BCF export. |
topic_status | bcf-status and indexed s | Must be valid in project extensions. |
topic_type | bcf-type | Must be valid in project extensions. |
priority | bcf-priority | Optional unless project profile requires it. |
stage | bcf-stage | Use local phase vocabulary, e.g. SIA/LP/UNI. |
assigned_to | p tag with marker assignee | Use pubkey, keep email in content only for import provenance. |
labels | repeated t tags and content.labels | t gives relay search. |
due_date | bcf-due, content.due_date | Unix timestamp in tag, ISO string in content. |
server_assigned_id | bcf-server-assigned-id | Human-visible ticket number, not identity. |
bim_snippet | content.bim_snippet plus optional e file ref | Keep full BCF object. |
document_references | e with marker document or kind:30903 | Prefer 30903 when metadata matters. |
related_topics | e with marker related | Reference topic event IDs. |
viewpoints | e with marker viewpoint | Reference kind:30901 event IDs. |
{
"kind": 30900,
"pubkey": "<author-pubkey-hex>",
"created_at": 1778946000,
"tags": [
["d", "b345f4f2-3a04-b43b-a713-5e456bef8228"],
["a", "30902:<owner-pubkey>:pilot-2026"],
["bcf-guid", "b345f4f2-3a04-b43b-a713-5e456bef8228"],
["bcf-version", "3.0"],
["bcf-status", "Open"],
["s", "Open"],
["bcf-type", "Clash"],
["bcf-priority", "High"],
["bcf-stage", "LP4"],
["bcf-due", "1779577200"],
["bcf-server-assigned-id", "P-2026-042"],
["p", "<mep-pubkey>", "", "assignee"],
["p", "<structural-pubkey>", "", "consulted"],
["t", "mep"],
["t", "structure"],
["ifc", "2Y$0aBv19FqQ9rP5J6wQhA"],
["ifc-file", "<ifc-file-ref-event-id>"],
["e", "<viewpoint-event-id>", "", "viewpoint"],
["e", "<snapshot-file-event-id>", "", "snapshot"]
],
"content": "{\"title\":\"Duct crosses main girder at axis 4/B\",\"description\":\"MEP duct intersects primary steel beam. Need reroute or structural opening decision.\",\"labels\":[\"mep\",\"structure\"],\"extensions\":{}}"
}
BCF comment mapping
BCF comments become regular immutable events. They reference the topic
by event ID with an e tag and carry the original BCF comment
GUID in content. If a tool imports an edited BCF comment, publish a new
comment event with an e marker replaces; do not
mutate the original signed event.
| BCF comment field | Nostr location | Rule |
|---|---|---|
guid | content.guid, optional bcf-guid | Preserve for export. |
topic_guid | e marker root and content.topic_guid | Use event ID for live graph, BCF GUID for export. |
comment | content.text | Must not be blank unless comment only links a viewpoint. |
author | pubkey, optional content.author_email | Pubkey is authoritative. |
date | created_at, content.date | Use Unix seconds on the event. |
viewpoint_guid | e marker viewpoint | Optional. |
modified_date | replacement event + content.modified_date | Keep original event and replacement chain. |
{
"kind": 1170,
"pubkey": "<structural-pubkey>",
"created_at": 1778950200,
"tags": [
["e", "<topic-event-id>", "", "root"],
["a", "30902:<owner-pubkey>:pilot-2026"],
["p", "<mep-pubkey>"],
["e", "<viewpoint-event-id>", "", "viewpoint"]
],
"content": "{\"guid\":\"a333fca8-1a31-caac-a321-bb33abc8414\",\"topic_guid\":\"b345f4f2-3a04-b43b-a713-5e456bef8228\",\"text\":\"Girder HEB400 cannot be lowered. Please reroute duct below suspended ceiling zone.\",\"date\":\"2026-05-16T10:10:00Z\"}"
}
BCF viewpoint mapping
The viewpoint captures how to reopen the problem in a model viewer: camera, clipping, annotations, selected components, visible components, colors and optional bitmap/snapshot references. BCF API treats viewpoints as immutable; this profile follows that rule.
| BCF viewpoint field | Nostr location | Rule |
|---|---|---|
guid | d, bcf-guid, content.guid | One event per GUID. |
perspective_camera | content.camera.perspective | BCF object copied losslessly. |
orthogonal_camera | content.camera.orthogonal | Exactly one camera type when camera is present. |
components.selection | repeated ifc tags + content.components.selection | Tags make selected elements searchable. |
components.visibility | content.components.visibility | Keep exception list and default visibility. |
components.coloring | content.components.coloring | Preserve ARGB color values. |
lines | content.lines | Copy point arrays. |
clipping_planes | content.clipping_planes | Direction must not be zero vector. |
snapshot | e marker snapshot to kind:1063 | File hash lives in NIP-94 event. |
{
"kind": 30901,
"pubkey": "<author-pubkey-hex>",
"created_at": 1778945700,
"tags": [
["d", "a11a82e7-e66c-34b4-ada1-5846abf39133"],
["a", "30902:<owner-pubkey>:pilot-2026"],
["bcf-guid", "a11a82e7-e66c-34b4-ada1-5846abf39133"],
["bcf-version", "3.0"],
["ifc", "2Y$0aBv19FqQ9rP5J6wQhA"],
["ifc", "1hJ9K0lMn2OP3Q4rS5tUvW"],
["ifc-file", "<ifc-file-ref-event-id>"],
["e", "<snapshot-file-event-id>", "", "snapshot"]
],
"content": "{\"guid\":\"a11a82e7-e66c-34b4-ada1-5846abf39133\",\"camera\":{\"type\":\"perspective\",\"camera_view_point\":{\"x\":12.4,\"y\":8.2,\"z\":3.1},\"camera_direction\":{\"x\":-0.8,\"y\":0.1,\"z\":-0.2},\"camera_up_vector\":{\"x\":0,\"y\":0,\"z\":1},\"field_of_view\":60,\"aspect_ratio\":1.77},\"components\":{\"selection\":[{\"ifc_guid\":\"2Y$0aBv19FqQ9rP5J6wQhA\"}],\"visibility\":{\"default_visibility\":true,\"exceptions\":[]}},\"clipping_planes\":[]}"
}
Worked example
The minimal useful BCF exchange is not one event. It is a small graph: file metadata for the snapshot, a viewpoint, the topic, then comments and audit records.
1. Snapshot file kind:1063
{
"kind": 1063,
"pubkey": "<author-pubkey-hex>",
"created_at": 1778945600,
"tags": [
["url", "https://blossom.example/files/snap-axis-4b.png"],
["m", "image/png"],
["x", "6f1e9d4a6f7b0a2c3d4e5f67890abcdeffedcba09876543210abcdef12345678"],
["size", "284102"],
["dim", "1440x900"],
["a", "30902:<owner-pubkey>:pilot-2026"],
["alt", "BCF snapshot showing duct crossing steel girder at axis 4/B"]
],
"content": "Snapshot for topic P-2026-042"
}
2. Topic current state kind:30900
Use the topic example above. The important relationships are:
e:viewpoint, e:snapshot, project a,
assignee p, and status mirror s.
3. Status update audit kind:1171
{
"kind": 1171,
"pubkey": "<mep-pubkey>",
"created_at": 1779032400,
"tags": [
["e", "<topic-event-id>", "", "root"],
["a", "30902:<owner-pubkey>:pilot-2026"],
["audit-field", "bcf-status"],
["audit-from", "Open"],
["audit-to", "InProgress"],
["bcf-action", "update"],
["client", "bim-cvp-bcf-client/0.1"]
],
"content": "{\"reason\":\"MEP team accepted the clash and is preparing reroute option.\"}"
}
4. Replacement topic state kind:30900
After the audit event, publish a new kind:30900 with the
same d, same bcf-guid, updated
bcf-status and updated s. Clients that only
need the current state read this latest topic. Clients that need evidence
read the 1171 chain.
Relay query patterns
These examples are Nostr filters. They are intentionally simple and assume the relay indexes single-letter tags as described in NIP-01.
All current BCF topics in one project
{
"kinds": [30900],
"#a": ["30902:<owner-pubkey>:pilot-2026"],
"limit": 200
}
All open topics in one project
{
"kinds": [30900],
"#a": ["30902:<owner-pubkey>:pilot-2026"],
"#s": ["Open"],
"limit": 200
}
One topic thread
[
{
"kinds": [30900],
"#d": ["b345f4f2-3a04-b43b-a713-5e456bef8228"],
"limit": 1
},
{
"kinds": [1170, 1171],
"#e": ["<topic-event-id>"],
"limit": 500
}
]
All topics touching an IFC element
Standard NIP-01 relays should not be assumed to index multi-letter tags.
The portable query fetches project topics and filters ifc
tags client-side.
{
"kinds": [30900, 30901],
"#a": ["30902:<owner-pubkey>:pilot-2026"],
"limit": 500
}
#ifc. Clients may use it when
available, but must keep the project-scope fallback above.
BCF XML/API round-trip rules
BCF-XML to Nostr
- Read
bcf.version,project.bcfp,extensions.xmland topic folders. - Create or update one
kind:30902project event containing extension lists. - For every snapshot or document file, publish
kind:1063with hash, MIME type, size and URL. - For every viewpoint file, publish one
kind:30901; never republish the same viewpointd. - For every topic, publish the current
kind:30900state using the original topic GUID. - For every comment, publish
kind:1170and preserve comment GUID, author/date and optional viewpoint reference. - If the source contains history, publish
kind:1171audit events; otherwise mark import provenance in content.
Nostr to BCF-XML
- Resolve project
kind:30902and extension lists. - Resolve latest
kind:30900per topicd. - Resolve linked viewpoints and snapshots via
emarkers. - Sort comments by
created_at, then by event ID for tie-breaking. - Write
markup.bcf,viewpoint.bcfv, snapshot files and optionaldocuments.xml. - Keep unknown content fields as BCF extension data where possible; otherwise write an export warning.
BCF API adapter
The API adapter maps REST mutations to signed events: POST Topic
publishes 30900; PUT Topic publishes a new
30900 and a 1171; POST Comment
publishes 1170; POST Viewpoint publishes
30901; BCF API event endpoints read from 1171.
Validation checklist
| Check | Fail if | Why it matters |
|---|---|---|
| Topic identity | kind:30900 has no d or bcf-guid | Round-trip cannot preserve BCF GUID. |
| Project scope | Project events lack a reference | Relay queries cannot isolate a project. |
| Status validity | bcf-status not in project extensions | BCF export becomes invalid for the project. |
| Viewpoint immutability | Two 30901 events reuse one d | BCF viewpoint changed instead of creating a new one. |
| Snapshot integrity | Snapshot link has no 1063 with x hash | Image provenance cannot be verified. |
| IFC references | Topic references components but no ifc tags | Viewer cannot find elements efficiently. |
| Audit trail | Status/assignee/priority changed without 1171 | Current state exists but reason and author trail are missing. |
Read on
- Workflow model — where BCF events sit in the full process
- Alice, Bob, Max — the same process as a concrete story
- BCF primer — what BCF is before the event layer
- Kind mapping — the full registry beyond BCF
- Nostr basics — event shape, relays and signing
- IFC — the GUIDs BCF points at