Data model guide
Updated 19 August 2026
This page is guidance, not a requirement. The specification binds only what you publish — the manifest and the feed. Your internal model is yours: the crosswalk maps whatever you already have onto the shared schema, and nobody migrates your primary keys.
But many aid apps are built in days, during the emergency, by teams choosing table shapes at two in the morning. If you are building now — or can still adapt — this is the shape that makes publishing nearly free, because it is the published shape, held internally. Five recommendations, in the order they save you pain.
1. Separate what travels from what never travels
Keep personal contact — names, phones, emails, anything person-level — in its own tables, with no join path from the queries that build your feed. The protocol excludes person-level data by a join prohibition, and the cheapest way to comply is structural: a serializer that cannot reach contact data cannot leak it.
| Travels (feed tables) | Never travels (contact tables) |
|---|---|
| place name, kind, status | contact person’s name |
| municipality, neighborhood, coordinates | phone numbers, emails |
| quantities, confirmations, timestamps | personal media |
| the record’s public page URL | anything about an individual |
2. Shape your core entities like the protocol’s
Name and type your columns after the
place schema where you can: name,
place_kind, lifecycle_status, service_status, public_url. Every field
you share with the schema is a crosswalk row that becomes the identity
function. If your app handles help requests with quantities, know that
need/offer are proposed for v0.2 in RFC 0002 — a draft,
not shipped — and shaping yours after the RFC’s sketch
(quantity_required, quantity_covered, unit, a place or org anchor) means
you publish the day it lands.
3. Carry honesty fields natively
The protocol’s hardest rule to retrofit: last_confirmed_at must be a real
confirmation event — somebody checked — or null. Never a database
updated_at. So store the confirmation event from day one (who confirmed,
how, when), and keep status in a status column: a record named
“Coliseo (CLOSED!!)” encodes state in a name, and no schema can read it back
out.
4. Geography as code + text + coordinates + precision
Store all four, from day one: the municipality code
(DANE in Colombia — the protocol’s municipality_code), the municipality and
neighborhood as text, coordinates when you have them, and the
precision you actually have (geo_precision) rather than a precision you
wish you had. Free-text city names you cannot code yet are fine —
municipality_text with a null code is the schema’s escape hatch. This is
what lets a record republished in another city still say where it is.
5. Stable public ids, and a public page per record
Give every record an id that never changes (your primary key, any type) and a
public detail page — the public_url other apps’ action buttons will link
to. If a record has no public page, other apps have nowhere to send the
person who wants to act on it, and your contact rule has no mechanism.
The two on-ramps
- Existing app: don’t reorganize anything. Follow the quickstart — the crosswalk maps your columns as they are.
- New or adaptable app: start from this model, and the agent skill will find a crosswalk so close to identity that the serializer nearly writes itself.