GeoVivé Docs

API reference · v1

Overview

GeoVivé stores places as features (standard GeoJSON) grouped into datasets. Each dataset has an owner and is either public or private. The API is JSON over HTTPS.

Base URL: https://api.geovive.link

All paths below are under /v1. Public data needs no key; writes need a signed-in user's token.

Authentication

GeoVivé uses Amazon Cognito (OpenID Connect) with Google sign-in. Authenticated calls send the user's access token:

Authorization: Bearer <access_token>
SettingValue
Issuerhttps://cognito-idp.us-east-1.amazonaws.com/us-east-1_cLqbEZJhi
Sign-in domainhttps://auth.geovive.link
FlowAuthorization code + PKCE
TokenAccess token (token_use: access); the user is identified by its sub claim

These tokens are issued only to GeoVivé's own web app. Other apps get access to a user's maps through account linking, with the user's consent.

Data model

The app calls a dataset a map; this reference and the API say dataset. GeoVivé stores datasets of features (standard GeoJSON) and displays layers it doesn't store. The authoritative definition is datamodel.yaml (JSON Schema). openapi.yaml references it, and every write is checked against it.

ConceptData modelAPIIn the appAppConnect
FeatureFeature = geometry + FeatureProperties/v1/datasets/{id}/featuresA pin (point) or shape (line or area)Pins in a user's datasets
DatasetDataset + its FeatureCollection/v1/datasetsA "map": the Map menu and My mapsCreated for a user by an Open in GeoVivé link or account linking
LayerLayerList: a display contract (source, style, credits, license), not stored features/v1/apps/{appId}/layers, relayThe Layers panelThe app's own layers
DatasetLayer
Whose dataStored in GeoVivé, owned by a user or the GeoVivé libraryA government agency's or an AppConnect app's
EditableBy its ownerNo, display only
GeometryAny: points, lines, areasAny
Held to the data modelYes, strictlyNo: shown as published, after AppConnect's checks and approval

Feature properties

FieldRules
nameRequired, up to 200 characters
categoryRequired: location (a place), event (recorded activity) or alert (noteworthy activity)
descriptionMarkdown, up to 5,000 characters. Anything specific (address, hours, level) goes here.
eventTimeWhen it happened; required for events and alerts
statusactive (default), inactive or archived
severityAlerts only: info, warning or critical
source, externalIdWhere the feature came from, and its ID there
icon, colorOptional display overrides (emoji or icon name; #rrggbb)
id, datasetId, createdAt, updatedAtSet by the server

No other properties are accepted. Tags describe datasets (up to 10, for search), never features.

Access rules

Admin API

/v1/admin/* is internal, not a public API surface: it's undocumented on purpose and returns 404 for anyone not in the admins Cognito group, so its existence isn't revealed to other signed-in users. Members still need to set up and verify an app-based TOTP authenticator (independent of Cognito MFA) to get a short-lived admin session before any admin route works. It backs the /admin/ console: dataset review/visibility/delete with an audit log, platform overview, and (once built) agent controls. All routes are served by one API Gateway route (ANY /v1/admin/{proxy+}, dispatched internally) to stay under the Lambda's resource-policy size limit — a pattern worth reusing for any future route group here rather than adding many explicit routes.

Datasets

MethodPathAuthDescription
GET/v1/datasets/searchnoneSearch public datasets by name, description and tags: q, tag, limit (1–50, default 20)
GET/v1/datasets?scope=mine&ids=a,boptionalYour datasets, plus up to 25 other datasets you can read by id (what the map's menu uses)
GET/v1/datasetsoptionalEvery public dataset plus your own. Fine for small catalogs; prefer search as the catalog grows
POST/v1/datasetsrequiredCreate a dataset (you become the owner). Returns 201
GET/v1/datasets/{datasetId}optionalOne dataset
PATCH/v1/datasets/{datasetId}ownerUpdate name, description, tags or visibility
DELETE/v1/datasets/{datasetId}ownerDelete the dataset and all its features. Returns 204

Create / update body

{
  "name": "Scouting spots",          // required on create
  "description": "Optional text",     // up to 1,000 characters
  "tags": ["hiking", "colorado"],     // up to 10; lowercase letters, numbers, spaces, dashes
  "visibility": "private"            // "private" (default) or "public"
}

Tags are normalized: lowercased, accents removed, duplicates dropped. A comma-separated string is accepted too.

Dataset object

{
  "datasetId": "world-capitals",
  "name": "World Capitals",
  "description": "National capitals of the world (Natural Earth)…",
  "tags": ["reference", "world", "capitals", "cities", "countries"],
  "visibility": "public",
  "ownerId": "system",
  "source": "https://www.naturalearthdata.com/",
  "featureCount": 215,
  "createdAt": "2026-09-26T01:51:59.386Z",
  "updatedAt": "2026-09-26T01:51:59.386Z"
}
GET /v1/datasets/search?q=colorado%20trails&tag=hiking&limit=20

{
  "total": 3,              // public datasets that matched
  "complete": true,
  "results": [ { …dataset… } ],
  "tags": [ { "name": "hiking", "count": 3 } ]   // tags among the matches, for filtering
}

Full-text search over each public dataset's name, tags and description. It matches partial words (trail finds "Trailheads"), simple word forms (schools finds "school"), small typos, and ignores accents and case. Results rank by relevance, with name matches weighted most, then tags, then description. When no dataset matches every word, datasets matching any word are returned. With no q, results are the largest datasets first. Only public datasets are searched.

Features

MethodPathAuthDescription
GET/v1/datasets/{datasetId}/featuresoptionalA FeatureCollection. Query: limit (default 500, max 1000), nextToken
POST/v1/datasets/{datasetId}/featuresownerAdd a feature. GeoVivé assigns the ID. Returns 201
GET/v1/datasets/{datasetId}/features/{featureId}optionalOne feature, always with full-detail geometry. Query: detail=simplified for the preview
PUT/v1/datasets/{datasetId}/features/{featureId}ownerReplace a feature's geometry and properties
DELETE/v1/datasets/{datasetId}/features/{featureId}ownerDelete a feature. Returns 204

Pagination: when more results exist, the collection includes nextToken. Pass it back as a query parameter to get the next page.

Bulk delete, move and copy

POST /v1/datasets/{datasetId}/features/batch
{ "action": "move", "ids": ["f1", "f2"], "targetDatasetId": "d2" }   // "delete" | "move" | "copy"

{ "action": "move", "done": ["f1", "f2"], "failed": [], "targetDatasetId": "d2" }

Owner only; for move and copy you must also own the target. Up to 100 IDs per call. Moves and copies keep full geometry and properties, with new feature IDs in the target. Each ID succeeds or fails on its own, and pin counts are updated.

Formats

Feature (request)

{
  "type": "Feature",
  "geometry": { "type": "Point", "coordinates": [-104.9903, 39.7392] },
  "properties": {
    "name": "Trailhead",           // required
    "category": "location",        // event | location | alert (others are stored but not styled)
    "description": "Parking for 10 cars"
    // any other properties are stored and returned as-is
  }
}

Feature (response)

GeoVivé adds id, datasetId, createdAt and updatedAt to properties, and sets the feature's top-level id. These are ignored if sent in a request. Non-point features include a GeoJSON bbox of the full shape.

Errors

Errors return JSON: { "message": "…" }.

StatusMeaning
400Invalid body, geometry or query parameter
401Missing, invalid or expired token
403Signed in, but not the dataset's owner
404Not found, or a private dataset you can't see
413Geometry over 5 MB
500Server error

Examples

Read a public dataset

curl https://api.geovive.link/v1/datasets
curl "https://api.geovive.link/v1/datasets/world-capitals/features?limit=50"

Create a dataset and add a pin

TOKEN="<access token>"

curl -X POST https://api.geovive.link/v1/datasets \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"Scouting spots","visibility":"private"}'

curl -X POST https://api.geovive.link/v1/datasets/<datasetId>/features \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"type":"Feature","geometry":{"type":"Point","coordinates":[-104.99,39.74]},
       "properties":{"name":"Trailhead","category":"location"}}'

JavaScript

const res = await fetch("https://api.geovive.link/v1/datasets/world-capitals/features");
const collection = await res.json();   // GeoJSON FeatureCollection
map.getSource("places").setData(collection);

Imports

Load outside data into a dataset you own. Imports run in the background: start one, then poll it until status is succeeded or failed.

MethodPathAuthDescription
POST/v1/datasets/{datasetId}/imports/upload-urlownerA presigned URL for uploading a GeoJSON file (PUT it within 15 minutes)
POST/v1/datasets/{datasetId}/importsownerStart an import. Returns 202 with the import
GET/v1/datasets/{datasetId}/imports/{importId}ownerStatus, counts and the first few problems
// A state agency's ArcGIS layer, filtered, replacing what's in the map
{
  "source": { "type": "arcgis", "url": "https://…/FeatureServer/0", "where": "STATE = 'CO'" },
  "mode": "replace",            // or "append" (default)
  "nameField": "UNIT_NAME",     // optional; otherwise name/title/…name fields are tried
  "categoryField": "TYPE"       // optional; copied into properties.category
}
// or { "source": { "type": "url", "url": "https://…/data.geojson" } }
// or { "source": { "type": "upload", "key": "<from upload-url>" } }

Exports

POST /v1/datasets/{datasetId}/exports with { "format": "geojson" | "kml" | "gpx" } returns a download link valid for an hour. No sign-in is needed for public datasets; send your token for private ones.

Layer lists (connected apps)

GeoVivé keeps no subject-matter layers of its own. A connected app brings its layers by publishing a layer list, a JSON file on the app's own verified domain, registered through AppConnect (for example https://hunt.example.com/geovive-layers.json). GeoVivé shows those layers in the AppConnect section of the Layers panel, only to people working with that app: users who arrived through the app's open link, or who open a map the app created.

No personal information. Labels and shown fields must not identify individuals (names, emails, phone numbers, home addresses). The connection checks look for it, and the AppConnect terms forbid it.

{
  "version": 1,
  "title": "Example layers",
  "layers": [
    {
      "id": "co-gmu",                       // letters, numbers, - or _
      "name": "GMUs (big game)",
      "group": "Colorado",                  // heading in the Layers panel
      "color": "#f59e0b",
      "description": "Big game units with DAUs.",
      "attribution": "Colorado Parks and Wildlife",
      "license": "Official agency data",
      "source": { "type": "arcgis", "url": "https://…/FeatureServer/6", "where": "1=1" },
      "label": "GMU {GMUID}",               // shown on the map; {FIELD} from the source
      "fields": { "COUNTY": "County", "ELKDAU": "Elk DAU" },   // shown when clicked
      "delivery": "relay",                  // or "direct"
      "cacheHours": 24,                     // relay only (1–168)
      "tolerance": 0.0005                   // simplification in degrees (optional)
    }
  ]
}
MethodPathDescription
GET/v1/apps/{appId}/layersThe app's layers as GeoVivé shows them (display settings; sources only for direct layers)
GET/v1/relay/{appId}/{layerId}A one-hour link to the relayed layer file (GeoJSON, each feature with _label and the listed fields)

A registered app can send its signed-in users to their own GeoVivé map for one item in the app (for example, a hunt plan), then bring them back.

https://geovive.link/open?app=<appId>&ref=<itemId>
  &title=<map name>
  &area=<URL of a GeoJSON outline>
  &return=<URL to come back to>
ParameterRequiredMeaning
appyesYour registered app ID
refyesYour ID for the item. The same user + app + ref always opens the same map.
titlenoName for the map when it's first created
areanoGeoJSON drawn as a read-only outline (never stored as pins). Must be served from one of your registered origins, with CORS allowing https://geovive.link.
returnnoWhere Done sends the user. Must be on one of your registered origins.

GeoVivé signs the user in if needed, finds or creates their private map, applies your pin types, and shows a banner. Done returns them to return with ?geovive_map=<datasetId> added. Only the ID travels in the URL.

The map belongs to the user. Until connected apps ship (below), your app can't read it with its own credentials; the user sees it in GeoVivé under My maps.

Showing the app's layers

Add layers=<layerId,…> to turn on layers from your layer list, and focus=<layerId>:<label> to zoom to and outline one feature, for example focus=co-gmu:GMU 61. Without ref, the link is a view link: no sign-in and no map is created; GeoVivé just shows your layers, with a Done button back to return.

https://geovive.link/open?app=<appId>&layers=co-gmu,co-sheep&focus=co-gmu:GMU%2061&return=https%3A%2F%2Fhunt.example.com%2Funits%2F61

API used by the link

MethodPathAuthDescription
GET/v1/apps/{appId}noneApp name, allowed origins and pin types
PUT/v1/apps/{appId}/maps/{ref}requiredFind or create the caller's map for that item. Body: title, externalUrl, referenceArea. Returns 201 when created, 200 when it already existed.

To connect an app, see AppConnect requirements for what to have ready.

AppConnect API

The AppConnect page uses these routes; all but the webhook need a signed-in user, who becomes the app's owner. See requirements and terms.

MethodPathDescription
POST/v1/appconnect/appsRegister an app: appId, name, domain, layersUrl, returnOrigins, contactEmail, acceptTerms. Returns the domain-verification token.
GET/v1/appconnect/appsYour apps and their stages
GET/v1/appconnect/apps/{appId}One app with its history
POST/v1/appconnect/apps/{appId}/checksRun the connection checks (202); the report is emailed and listed on the app
GET/v1/appconnect/apps/{appId}/reports/{reportId}A 10-minute link to a report or notice PDF
POST/v1/appconnect/apps/{appId}/disconnectDisconnect now or at term end (confirm must equal the app ID); DELETE cancels a scheduled one
POST/v1/appconnect/apps/{appId}/reconnectReconnect within 90 days
GET/v1/appconnect/apps/{appId}/exportSettings and history as JSON

Stages: registered → checking → checks passed or needs fixes → awaiting payment → live, renewed yearly; plus sandbox, expired, suspended, disconnected (90-day reconnect window) and closed.

Account linking

A live or sandbox AppConnect app can ask a user to link their GeoVivé account. With the user's consent, your app can read the maps it created for them (and any they choose to share), and create maps and pins. It's standard OAuth 2.0: authorization code with PKCE, and your client secret stays on your server.

1. Set up

On your app's AppConnect page, under Account linking, add your redirect URIs (exact https addresses on your verified domain; http://localhost is allowed while in the sandbox) and create a client secret. The secret is shown once. Your client ID is your app ID.

2. Send the user to GeoVivé

https://geovive.link/connect/?response_type=code
  &client_id=YOUR_APP_ID
  &redirect_uri=https%3A%2F%2Fyourapp.example%2Fgeovive%2Fcallback
  &scope=profile%20maps%3Aread%20maps%3Awrite
  &state=RANDOM_PER_REQUEST
  &code_challenge=BASE64URL(SHA256(code_verifier))
  &code_challenge_method=S256

The user signs in if needed, sees your app's name, domain and what it's asking for, and can share extra maps read-only. They come back to your redirect_uri with ?code=…&state=…, or ?error=access_denied if they cancel. Check that state matches.

ScopeLets your app
profileGet a user ID for your app and the user's display name
maps:read (default)Read maps your app created for this user, and maps the user shared with it
maps:writeCreate maps, and add, change and delete pins in maps your app created

3. Swap the code for tokens (on your server)

POST https://api.geovive.link/v1/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=…&code_verifier=…
&redirect_uri=…&client_id=YOUR_APP_ID&client_secret=…
{ "access_token": "gva_…", "token_type": "Bearer", "expires_in": 3600,
  "refresh_token": "gvr_…", "scope": "maps:read maps:write profile" }

Codes last 5 minutes and work once. Access tokens last an hour. Refresh tokens last 90 days and are replaced every time you use one (grant_type=refresh_token): store the new one. Using an old refresh token again is treated as theft and ends the link. HTTP Basic client authentication also works. Revoke a token with POST /v1/oauth/revoke (token, plus your client credentials).

4. Call the linked API

Authorization: Bearer gva_…
MethodPathScopeDoes
GET/v1/linked/meanyuserId (stable for your app only), scopes, and displayName with profile
GET/v1/linked/datasetsmaps:readMaps your app can see; createdByThisApp says which you can change
POST/v1/linked/datasetsmaps:writeCreate a private map: { name, description?, tags?, externalRef? }. The same externalRef returns the existing map
GET/v1/linked/datasets/{id}, …/features, …/features/{featureId}maps:readSame shapes as the public dataset and feature routes
POST, PUT, DELETE/v1/linked/datasets/{id}/features[/{featureId}]maps:writeAdd, replace or delete pins, only in maps your app created

What your app never gets: the user's email, password, GeoVivé account ID, or any map they didn't create through you or share with you. userId is different for every app, so apps can't match users with each other.

When access ends: the user removes your app in My data → Connected apps, your app leaves AppConnect or lapses, or a refresh token is reused. Calls then return 401; send the user through step 2 again. Maps your app created stay with the user.