Source subscriptions
An ordinary subscription watches entities. A context source subscription watches the registrations, the announcements other brokers and sources have made about what they hold. When a matching registration appears, changes or goes, the subscription's receiver hears about it, with the registration document in the notification. This is the control plane of a federation. Build a routing table, refresh it, or react to a new source joining, without polling.
The specification defines them beside the entity subscriptions, under clause 5.11, with their own resource, /ngsi-ld/v1/csourceSubscriptions.
Subscribing
The body has the same shape as an entity subscription, and its entities selector is matched against the entities each registration covers.
- http
- curl
- rust
- python
POST /ngsi-ld/v1/csourceSubscriptions
Content-Type: application/json
{
"id": "urn:ngsi-ld:Subscription:1",
"type": "Subscription",
"entities": [
{
"type": "Building"
}
],
"notification": {
"endpoint": {
"uri": "https://manager.example.org/notify",
"accept": "application/json"
}
},
"timeInterval": 2
}
curl -i http://localhost:1056/ngsi-ld/v1/csourceSubscriptions \
-H 'Content-Type: application/json' \
-d '{
"id": "urn:ngsi-ld:Subscription:1",
"type": "Subscription",
"entities": [
{
"type": "Building"
}
],
"notification": {
"endpoint": {
"uri": "https://manager.example.org/notify",
"accept": "application/json"
}
},
"timeInterval": 2
}'
let endpoint = Endpoint::builder("https://manager.example.org/notify")
.accept(NgsiLdMediaType::Json)
.build()?;
let subscription = Subscription::registration_builder()
.id(iri!("urn:ngsi-ld:Subscription:1"))
.entities(["Building"])
.time_interval(2)
.notification(NotificationParams::builder(endpoint).build()?)
.build()?;
let outcome = client
.registration_subscriptions()
.create(&subscription)
.send()
.await?;
response = requests.post(
f"{BASE}/csourceSubscriptions",
json={
"id": "urn:ngsi-ld:Subscription:1",
"type": "Subscription",
"entities": [
{
"type": "Building",
},
],
"notification": {
"endpoint": {
"uri": "https://manager.example.org/notify",
"accept": "application/json",
},
},
"timeInterval": 2,
},
)
assert response.status_code == 201
HTTP/1.1 201 Created
Location: /ngsi-ld/v1/csourceSubscriptions/urn:ngsi-ld:Subscription:1
What arrives
The notification is a ContextSourceNotification, and its data holds the matching registration documents.
{
"id": "urn:ngsi-ld:ContextSourceNotification:1",
"type": "ContextSourceNotification",
"subscriptionId": "urn:ngsi-ld:Subscription:1",
"notifiedAt": "2026-10-26T08:00:00.000Z",
"data": [
{
"id": "urn:ngsi-ld:ContextSourceRegistration:1",
"type": "ContextSourceRegistration",
"information": [
{
"entities": [
{
"idPattern": "urn:ngsi-ld:Building:.*",
"type": "Building"
}
]
}
],
"endpoint": "https://source.example.org/ngsi-ld"
}
]
}
A periodic subscription fires on creation with the registrations that already match, not after the first interval has passed, and then again every timeInterval seconds whether anything changed or not, reporting an empty match set when nothing matches. A subscription without a timeInterval is change-driven and stays silent while nothing matches, so every notification it sends is news.
Choosing what matches
The selector narrows to the registrations worth hearing about.
entitiesmatches the registrations covering that kind of entity, bytype,idoridPattern.qfilters on the covered entities, in the same grammar an entity subscription uses.csffilters on the registration's own properties, read by their written keys.georel,geometryandcoordinatesselect registrations whose covered area reaches into a place.timerelwithtimeAtselects by the management interval the registration declares,afterfor observations later than the instant,beforefor management history earlier than it.
Periodic, paused and expired
A subscription reads back with a status of active, paused or expired. One created without isActive is active, "isActive": false starts it paused, and a paused periodic subscription sends nothing until resumed. An expiresAt in the past, or one that passes while it runs, turns the status expired and the notifications stop.
timeInterval and throttling do not combine. A subscription with both is refused with 400 BadRequestData, because throttling spaces change-driven notifications and there is nothing to space on a clock.
Managing them
GET /ngsi-ld/v1/csourceSubscriptions/{id} reads one back, with options=sysAttrs adding its timestamps. GET /ngsi-ld/v1/csourceSubscriptions lists them, paginated like any listing. PATCH changes the members a client may set, an empty fragment changing nothing, and DELETE removes it. Every operation behaves like its entity-subscription counterpart, so the same client code drives both, pointed at a different path.