Status: DRAFT. This guide specifies the Edufeed extension of NIP-52 ("NIP-52-Edufeed") based on the agreements in edufeed-app issue #13. Attribute semantics follow the issue agreements; the tag names (
registrationRequired,price,eventAttendanceMode,educationalLevel:*) are fixed. Nothing here is implemented on the relays yet — see Status.
This guide covers the write side of educational calendar events: how to publish NIP-52 calendar events enriched with educational metadata into the Edufeed data pool. The read side — fetching and rendering — is shown in the Educational Calendar Events live demo. Both describe the same events (the demo reads the plain NIP-52 events published today; the extension attributes below are a draft).
It is the calendar counterpart of the
Educational Data Pool integration guide,
which covers AMB resources (kind 30142 / NIP-AMB). Calendar events use the standard Nostr
calendar kinds and stay fully readable for any NIP-52 client; the educational
attributes are additive.
Educational events are ordinary NIP-52 events. Two kinds carry the events
themselves (both are addressable, identified by kind:pubkey:d-tag, and
updated by re-publishing the full event — same mechanics as described in
Section 4 of the data-pool guide):
| Kind | Meaning | start / end format |
|---|---|---|
31922 |
date-based (full-day / multi-day) | YYYY-MM-DD (end exclusive) |
31923 |
time-based | unix timestamp seconds, plus optional start_tzid / end_tzid |
Standard NIP-52 tags used on Edufeed events today (as found live on the relay):
["d", "<stable event id, often the source URL>"]
["title", "<event title>"]
["start", "..."] ["end", "..."] ["start_tzid", "Europe/Berlin"]
["summary", "<short description>"] // long form goes into content
["location", "<place or meeting URL>"]
["image", "<preview image URL>"]
["r", "<link to the event page>"] // repeated
["t", "<hashtag>"] // repeated
Everything in the next section comes on top of this.
NIP-52 itself has no fields for registration, cost, attendance mode, or educational classification. The Edufeed working group agreed on the following attributes (issue #13, calls of 2025-03-25 and 2025-05-22). Two tag styles are used, on purpose:
- Plain event attributes (registration, price, mode) are flat tags with
schema.org-aligned camelCase names where a schema.org counterpart exists
(
eventAttendanceMode,registrationRequired);pricefollows the NIP-15 / NIP-99 shape. - Vocabulary attributes (educational level, later: Bildungsbereiche,
Zielgruppen) reuse the NIP-AMB flattening convention
(
<property>:id/<property>:prefLabel:<lang>/<property>:typeconcept triples), so the same tooling that reads kind30142resources — includingamb-nostr-converter— can read them off calendar events. Their value lists are SKOS vocabularies published on nocabs.edufeed.org, Edufeed's Nostr vocabulary hosting — the same place the EKW vocabularies live (see ekw-metadata.md).
Whether attending requires signing up beforehand. Registration details
(how, where, deadlines) belong in the description text and/or an r-tag
linking to the event page — there is deliberately no structured
registration-link field.
["registrationRequired", "true"] // or "false"
- Tag absent → no statement.
Modelled on the price / currency fields of
NIP-15,
encoded as one tag (shape follows NIP-99's price tag):
["price", "<amount>", "<ISO-4217 currency>"] // e.g. ["price", "0", "EUR"]
Agreed semantics:
price value |
Meaning |
|---|---|
| tag absent / empty amount | keine Angabe (no statement) |
0 |
kostenlos (free) |
> 0 |
kostenpflichtig (paid) |
There is no separate "Kostenbeschreibung" field and no
isAccessibleForFree flag — anything beyond the number (reduced rates,
member prices, "teilweise kostenpflichtig") goes into the description text.
For events with differentiated pricing where no single number is honest,
publish the tag without an amount statement (or the base price) and
explain in the description. A structured per-audience price list
(Personenkreis / Kosten / Währung) was discussed and deferred — see
open questions.
Tag name and values are taken exactly from schema.org
eventAttendanceMode:
["eventAttendanceMode", "https://schema.org/OnlineEventAttendanceMode"]
| Issue-#13 term | Tag value |
|---|---|
| online | https://schema.org/OnlineEventAttendanceMode |
| offline | https://schema.org/OfflineEventAttendanceMode |
| mix | https://schema.org/MixedEventAttendanceMode |
mixcovers what other systems call blended or hybrid; details are expected on the organizer's event page.
NIP-52 already defines participant p-tags with a role field. Edufeed
supports (at least) these two roles:
["p", "<32-byte hex pubkey>", "<optional relay hint>", "organizer"]
["p", "<32-byte hex pubkey>", "<optional relay hint>", "attendee"]
Note: an attendee p-tag is set by the event author and is independent of
NIP-52 RSVP events (kind 31925), which attendees publish themselves.
The stage of the education system the event addresses. Uses the AMB property
name educationalLevel with the
Bildungsstufe vocabulary on nocabs —
a SKOS concept scheme that keeps the canonical KIM concept URIs
(https://w3id.org/kim/educationalLevel/),
so the :id values below are the same whether you resolve them via nocabs or
via w3id.org. Flattened exactly as on kind 30142 resources (see
Section 5 of the data-pool guide) —
a concept triple per value, multi-valued by repeating the triple:
["educationalLevel:id", "https://w3id.org/kim/educationalLevel/level_C"]
["educationalLevel:prefLabel:de", "Fortbildung"]
["educationalLevel:type", "Concept"]
The vocabulary as served by nocabs (ISCED-2011-aligned where applicable):
Concept URI (…/educationalLevel/) |
prefLabel de | prefLabel en |
|---|---|---|
level_0 |
Elementarbereich | Early childhood education |
level_1 |
Primarbereich | Primary education |
level_2 |
Sekundarbereich I | Lower secondary education |
level_3 |
Sekundarbereich II | Upper secondary education |
level_4 |
Postsekundarer nicht-tertiärer Bereich | Post-secondary non-tertiary education |
level_5 |
Kurzes tertiäres Bildungsprogramm | Short-cycle tertiary education |
level_A |
Hochschule | University |
level_6 |
— Bachelor oder äquivalent (narrower of level_A) |
Bachelor or equivalent |
level_7 |
— Master oder äquivalent (narrower of level_A) |
Master or equivalent |
level_8 |
— Promotion oder äquivalent (narrower of level_A) |
Doctoral or equivalent |
level_B |
Vorbereitungsdienst | Preparatory service |
level_C |
Fortbildung | Advanced training |
As everywhere in the data pool: the :id URI is authoritative,
:prefLabel:de is a denormalised display copy, :type is the literal
Concept.
A time-based (kind 31923) online training, free of charge, registration
required, aimed at teacher training (Fortbildung) and preparatory service:
{
"kind": 31923,
"tags": [
["d", "https://relilab.org/relilab-werkstatt-2026-09/"],
["title", "reli.werkstatt – Du bist kostbar!"],
["start", "1789651800"],
["start_tzid", "Europe/Berlin"],
["end", "1789655400"],
["end_tzid", "Europe/Berlin"],
["summary", "Kurze thematische Inputs mit Austausch und Praxis. Anmeldung über relilab.org; Zugangslink nach Anmeldung."],
["location", "Zoom: https://relilab.org/live"],
["image", "https://relilab.org/wp-content/uploads/werkstatt.jpg"],
["r", "https://relilab.org/relilab-werkstatt-2026-09/"],
["t", "relilab"],
["registrationRequired", "true"],
["price", "0", "EUR"],
["eventAttendanceMode", "https://schema.org/OnlineEventAttendanceMode"],
["p", "<organizer pubkey hex>", "wss://relay.edufeed.org", "organizer"],
["educationalLevel:id", "https://w3id.org/kim/educationalLevel/level_C"],
["educationalLevel:prefLabel:de", "Fortbildung"],
["educationalLevel:type", "Concept"],
["educationalLevel:id", "https://w3id.org/kim/educationalLevel/level_B"],
["educationalLevel:prefLabel:de", "Vorbereitungsdienst"],
["educationalLevel:type", "Concept"]
],
"content": "Die reli.werkstatt bietet kurze thematische Inputs und die Möglichkeit zu Austausch und Praxis.\n\nAnmeldung erforderlich (kostenlos): https://relilab.org/relilab-werkstatt-2026-09/"
}A NIP-52 client that knows nothing about the extension renders this as a normal calendar event; an education-aware client can additionally filter and display registration, cost, mode, and Bildungsstufe.
A second, complete example built from a real relilab event lives in
examples/relilab-impuls-kirchengeschichte-suedwesten-1.json.
As with AMB resources, any client that can sign for the author can publish — directly with the private key, via NIP-07 (browser extension), or via NIP-46 (remote signer). End-to-end from the command line with nak:
nak event -k 31923 \
-d "https://example.org/events/my-training" \
-t "title=Einführung in OER" \
-t "start=1789651800" \
-t "start_tzid=Europe/Berlin" \
-t "registrationRequired=true" \
-t "eventAttendanceMode=https://schema.org/OnlineEventAttendanceMode" \
-t "price=0;EUR" \
-t "educationalLevel:id=https://w3id.org/kim/educationalLevel/level_C" \
-t "educationalLevel:prefLabel:de=Fortbildung" \
-t "educationalLevel:type=Concept" \
-c "Eine offene Fortbildung zu Open Educational Resources. Anmeldung: siehe Veranstaltungsseite." \
--sec nsec1... \
wss://dev.amb-relay.edufeed.org(Multi-element tags like price use ; as separator in nak -t:
-t "price=0;EUR" produces ["price", "0", "EUR"]. Alternatively, pipe a
partial event JSON into nak event on stdin, or use one of the
client libraries.)
The command above targets the development relay
wss://dev.amb-relay.edufeed.org — use it while testing; real events go to
the production relay wss://relay.edufeed.org.
Updates re-publish the full event under the same d-tag; deletion is NIP-09
(kind 5 with an a-tag 31923:<pubkey>:<d>), identical to the
data-pool guide, Section 4.
Fetch by kind (a standard NIP-01 filter) and match the extension tags client-side:
// Client-side: keep only events for a given educational level
const wanted = "https://w3id.org/kim/educationalLevel/level_C";
const hits = events.filter(ev =>
ev.tags.some(t => t[0] === "educationalLevel:id" && t[1] === wanted));Once the calendar relay indexes the new tag keys — part of the planned relay
adaptation (issue #13: "Kalender-Relay anpassen") — the same selection works
relay-side. Note that multi-character tag filters like the following go beyond
plain NIP-01 (which only defines single-letter #x filters); they are an
Edufeed relay extension, the same one the AMB relay already answers for keys
like #about:id:
// Future, once the relay indexes the key — not implemented yet
{"kinds": [31922, 31923], "#educationalLevel:id": ["https://w3id.org/kim/educationalLevel/level_C"], "limit": 500}Notes:
- "Upcoming" cannot be filtered relay-side:
since/untilmatchcreated_at, not thestarttag. Fetch and filter client-side (this is what the live demo does).
Agreed in issue #13 (semantics): registration yes/no with details in the
description; price + currency with the empty/0/>0 semantics; attendance mode
online/offline/mix; p-tag roles organizer/attendee; educationalLevel as an
additional vocabulary attribute.
Fixed tag names (decided 2026-07-06): registrationRequired (true/false),
price (NIP-15 semantics, NIP-99 shape), eventAttendanceMode (name and
values exactly as schema.org), educationalLevel:* (NIP-AMB concept triples).
Open / follow-ups:
- Adapt the calendar relay (tag indexing for the new keys).
- Structured per-audience pricing (Personenkreis/Kosten/Währung list) — could
become repeated
pricetags with a label element; deferred, description text is the interim answer. - Further vocabulary attributes: Bildungsbereiche, Zielgruppen (audience) —
like
educationalLevel, each to be published as a SKOS vocabulary on nocabs.edufeed.org and flattened as NIP-AMB concept triples. - Import relilab dates into edufeed.