Documentation

Open GeoFeed Protocol

Conceptual guide to OGP's core primitives. For formal schemas and field-level specs, see the Protocol Spec.

Overview

OGP is built on a simple premise: every piece of local activity — a restaurant's daily special, a store opening, a neighborhood event — happens at a specific place in a specific part of the world. OGP gives that place a stable, human-readable identifier and defines how activity feeds anchored to it are published, discovered, and consumed.

The protocol has three layers:

Namespace

A geographic hierarchy of slugs — SubdivisionPaths. These are the primary keys of the protocol.

Registry

A DNS-like root that maps paths to authoritative content servers. Operated by geofeed.ai, open to anyone.

Feeds

ActivityPub-based activity streams published by any actor (business, person, government) for any path.

The registry and the protocol spec are operated by geofeed.ai and licensed Apache 2.0. Any implementation — including Findera — is a conforming server that can register with the public registry.

SubdivisionPath

A SubdivisionPath is the canonical identifier for a geographic subdivision. It is a slash-separated sequence of lowercase slugs, starting with an ISO 3166-1 alpha-3 country code.

Validation pattern
^[a-z]{2,3}(?:/[a-z0-9-_]+)*$
Examples
bra country (Brazil)
bra/sp state (São Paulo (state))
bra/sp/sao-paulo municipality (São Paulo (city))
bra/sp/sao-paulo/tatuape neighborhood (Tatuapé)
bra/sp/barueri/alphaville neighborhood (Alphaville)
usa/ca/san-francisco/mission neighborhood (Mission District)

Alpha-2 normalization

Paths may begin with a 2-letter country code (ISO 3166-1 alpha-2). They are silently normalized to alpha-3. br/sp/sao-paulo becomes bra/sp/sao-paulo. All canonical storage and API responses use alpha-3.

Short paths

Some contexts expose a short form that omits the country prefix when the country is implicit: sp/sao-paulo/tatuape. Short paths are display-only — canonical storage and all API responses always use the full path.

Subdivision Context

A SubdivisionPath is always resolved within a context. A context defines which geographic hierarchy is active for a country. The same physical subdivision can have different paths in different contexts.

Same neighborhood, two contexts
bra/sp/sao-paulo/tatuape context: BRA-administrative (default)
bra/sp/zona-leste/tatuape context: BRA-commercial (hypothetical)

Context IDs follow the format {COUNTRY}-{scheme}, e.g. BRA-administrative. Each country has exactly one default context. When a context is not specified in a request, the country's default context is used.

Context is never embedded in the path string itself. It is passed as a query parameter:

GET api.geofeed.ai/bra/sp/sao-paulo/tatuape ?context=BRA-administrative

Hierarchy & local_level

Every subdivision has a SubdivisionType with a numeric local_level that defines its position in the hierarchy. Higher values = more granular.

local_level type_id name (Brazil) example path
1 bra País bra
2 bra-state Estado (UF) bra/sp
3 bra-municipality Município bra/sp/sao-paulo
4 bra-district Distrito bra/sp/sao-paulo/se
5 bra-subdistrict Subdistrito bra/sp/sao-paulo/se/bela-vista
6 bra-neighborhood Bairro bra/sp/sao-paulo/tatuape

The minimal_local_level parameter on reverse geocoding endpoints controls response granularity. For example, minimal_local_level=3 returns at most the municipality level — useful when neighborhood precision is unnecessary or unavailable.

Parent-child relationships

Relationships are stored as a many-to-many through table scoped by context (SubdivisionHierarchy). A subdivision can have multiple parents within a context — e.g., a neighborhood that spans district boundaries. Root nodes are self-referencing: parent == child.

GeoFeed Format

GeoFeeds use the ActivityStreams 2.0 wire format (as used by ActivityPub). OGP extends it with a single geopath field on any Place or Object.

Activity — a business posts an update anchored to a neighborhood
{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://geofeed.ai/ns/v1"
  ],
  "type": "Create",
  "actor": {
    "type": "Organization",
    "id": "https://findera.app/actors/salao-gi-matos",
    "name": "Salão Gi Matos",
    "geopath": "bra/sp/sao-paulo/tatuape"
  },
  "object": {
    "type": "Note",
    "geopath": "bra/sp/sao-paulo/tatuape",
    "content": "Agenda aberta para esta semana!",
    "published": "2025-03-23T10:00:00Z"
  }
}

Because OGP uses ActivityStreams, any Fediverse client can consume an OGP feed without modification. The geopath field is additive — it doesn't break existing ActivityPub implementations that don't understand it.

Actor types

Any Schema.org type may be used for actors. Commonly used types:

LocalBusiness
FoodEstablishment
Organization
GovernmentOrganization
Person
Event

Registry Architecture

The registry operates like DNS root servers: it knows what geopaths exist and which servers are authoritative for them, but delegates actual content to registered servers. Anyone can register as a content server without permission from the registry operator.

DNS analogy
geofeed.ai/bra/sp/sao-paulo/tatuape
→ "this geopath exists; here are the servers that serve it"
findera.app/geo/bra/sp/sao-paulo/tatuape
→ "here are the actual activity feeds for this geopath"

Server types

index

Static presence — name, address, contact. No activity feed. Corresponds to Findera Presença (free tier).

feed

Activity feed — posts, events, offers anchored to the geopath. Updated periodically.

signal

Real-time feed — high-frequency updates, sensor data, live events. Corresponds to Findera Pro.

promoted:true

Promoted placement — shown above organic results in registry responses. Findera Ads layer.

API Endpoints

All endpoints are read-only (GET). The same data is served via three subdomains in different formats. Server registration is not yet open — contact geofeed.ai.

GET api.geofeed.ai/{geopath}

Returns registry metadata for a geopath.

context string optional SubdivisionContext ID. Defaults to country default.
GET api.geofeed.ai/{geopath}/children

Lists direct children of a geopath.

context string optional SubdivisionContext ID.
GET api.geofeed.ai/reverse/by_point

Reverse geocodes a coordinate to a geopath. Queries local DB first; falls back to Google Maps.

point string required Coordinates as lat,lng — e.g. -23.5478,-46.6166
minimal_local_level integer optional Limits response granularity. E.g. 3 = at most municipality.
GET api.geofeed.ai/reverse/by_path

Resolves a path to its full registry metadata.

path string required A valid SubdivisionPath.
context string optional SubdivisionContext ID.
GET api.geofeed.ai/reverse/by_address

Geocodes a free-form address to a geopath via Google Maps + local registry.

address string required Free-form address string.
country string optional ISO 3166-1 alpha-2 or alpha-3. Narrows geocoding scope.
GET api.geofeed.ai/autocomplete

Suggests geopaths as the user types. Returns signed tokens to avoid re-querying on selection.

query string required* Partial address or place name. Required if token not provided.
token string required* Signed place token from a previous autocomplete response.
session string optional Session ID for grouping calls. Generated if omitted.

Hint System

Reverse geocoding responses include a hint field that tells the consumer where the result originated:

internal

Result found in the local registry database. Fast, fully resolved, includes a Subdivision ID.

external

Result came from an external source (Google Maps). May not have a matching subdivision in the registry yet. A MissingGeoPath record is created for data quality tracking.

Both values are valid GeoPath responses. The subdivision field is only populated on internal hints. Consumers should handle both gracefully.