gemeinwert
BIM CVP

gemeinwert  /  wiki  /  mapping  /  bcf-events

BCF to Nostr event mapping

A concrete event vocabulary for carrying buildingSMART BCF 3.0 topics, comments, viewpoints, snapshots and audit trails as signed Nostr events. This is the event vocabulary for BCF-compatible coordination.

Core rule

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.

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

KindNameNostr classBCF objectAuthority
30902BCF projectaddressableProject + extensionslatest by d
30900BCF topicaddressableTopic / markuplatest by d
30901BCF viewpointaddressable, no republishVisualizationInfoone event per viewpoint GUID
1170BCF commentregularCommentappend-only
1171BCF auditregularTopic/comment event logappend-only
1063File metadataregularSnapshot, IFC, documenthash-bound file record
30903Document referenceaddressableDocumentReferencelatest by d
30904IFC file referenceaddressableBCF file/model reflatest by d
Profile rule for 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.

TagRequired onMeaningExample
d30900, 30901, 30902, 30903, 30904Addressable identity, normally BCF GUID["d","b345...8228"]
aall project eventsProject address["a","30902:<owner>:pilot-2026"]
e1170, 1171, linksReferenced event ID with optional marker["e","<topic-id>","","root"]
pwhen people are involvedParticipant, assignee, watcher or mentioned actor["p","<pubkey>","","assignee"]
s30900Indexed topic status mirror["s","Open"]
toptionalLabel, discipline, search facet["t","mep"]
bcf-guidBCF eventsOriginal BCF GUID["bcf-guid","b345...8228"]
bcf-versionBCF eventsBCF semantic version["bcf-version","3.0"]
bcf-status30900Project-extension status value["bcf-status","Open"]
bcf-type30900Project-extension topic type["bcf-type","Clash"]
bcf-priorityoptionalProject-extension priority["bcf-priority","High"]
bcf-stageoptionalPlanning / project stage["bcf-stage","LP4"]
bcf-dueoptionalDue date as Unix timestamp["bcf-due","1779577200"]
ifctopic/viewpointIFC GlobalId / compressed GUID["ifc","2Y$0aB..."]
ifc-filetopic/viewpointLinked kind:30904 file reference["ifc-file","<event-id>"]
x1063SHA-256 file hash["x","<sha256>"]
m1063MIME 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 fieldNostr locationRule
guidd, bcf-guid, content.guidPreserve exactly.
titlecontent.title, optional title tagHuman-readable issue headline.
descriptioncontent.descriptionMarkdown allowed in UI, plain string for BCF export.
topic_statusbcf-status and indexed sMust be valid in project extensions.
topic_typebcf-typeMust be valid in project extensions.
prioritybcf-priorityOptional unless project profile requires it.
stagebcf-stageUse local phase vocabulary, e.g. SIA/LP/UNI.
assigned_top tag with marker assigneeUse pubkey, keep email in content only for import provenance.
labelsrepeated t tags and content.labelst gives relay search.
due_datebcf-due, content.due_dateUnix timestamp in tag, ISO string in content.
server_assigned_idbcf-server-assigned-idHuman-visible ticket number, not identity.
bim_snippetcontent.bim_snippet plus optional e file refKeep full BCF object.
document_referencese with marker document or kind:30903Prefer 30903 when metadata matters.
related_topicse with marker relatedReference topic event IDs.
viewpointse with marker viewpointReference 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 fieldNostr locationRule
guidcontent.guid, optional bcf-guidPreserve for export.
topic_guide marker root and content.topic_guidUse event ID for live graph, BCF GUID for export.
commentcontent.textMust not be blank unless comment only links a viewpoint.
authorpubkey, optional content.author_emailPubkey is authoritative.
datecreated_at, content.dateUse Unix seconds on the event.
viewpoint_guide marker viewpointOptional.
modified_datereplacement event + content.modified_dateKeep 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 fieldNostr locationRule
guidd, bcf-guid, content.guidOne event per GUID.
perspective_cameracontent.camera.perspectiveBCF object copied losslessly.
orthogonal_cameracontent.camera.orthogonalExactly one camera type when camera is present.
components.selectionrepeated ifc tags + content.components.selectionTags make selected elements searchable.
components.visibilitycontent.components.visibilityKeep exception list and default visibility.
components.coloringcontent.components.coloringPreserve ARGB color values.
linescontent.linesCopy point arrays.
clipping_planescontent.clipping_planesDirection must not be zero vector.
snapshote marker snapshot to kind:1063File 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
}
Optional project relay extension: a project relay may advertise custom indexing for #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

  1. Read bcf.version, project.bcfp, extensions.xml and topic folders.
  2. Create or update one kind:30902 project event containing extension lists.
  3. For every snapshot or document file, publish kind:1063 with hash, MIME type, size and URL.
  4. For every viewpoint file, publish one kind:30901; never republish the same viewpoint d.
  5. For every topic, publish the current kind:30900 state using the original topic GUID.
  6. For every comment, publish kind:1170 and preserve comment GUID, author/date and optional viewpoint reference.
  7. If the source contains history, publish kind:1171 audit events; otherwise mark import provenance in content.

Nostr to BCF-XML

  1. Resolve project kind:30902 and extension lists.
  2. Resolve latest kind:30900 per topic d.
  3. Resolve linked viewpoints and snapshots via e markers.
  4. Sort comments by created_at, then by event ID for tie-breaking.
  5. Write markup.bcf, viewpoint.bcfv, snapshot files and optional documents.xml.
  6. 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

CheckFail ifWhy it matters
Topic identitykind:30900 has no d or bcf-guidRound-trip cannot preserve BCF GUID.
Project scopeProject events lack a referenceRelay queries cannot isolate a project.
Status validitybcf-status not in project extensionsBCF export becomes invalid for the project.
Viewpoint immutabilityTwo 30901 events reuse one dBCF viewpoint changed instead of creating a new one.
Snapshot integritySnapshot link has no 1063 with x hashImage provenance cannot be verified.
IFC referencesTopic references components but no ifc tagsViewer cannot find elements efficiently.
Audit trailStatus/assignee/priority changed without 1171Current state exists but reason and author trail are missing.

Read on

Sources