HTTP API

LocationNotes API documentation

Use this page when you need the real HTTP routes, query parameters, authentication rules, and copy-paste examples behind the website and Android app. The public site, authenticated website flows, and Android client all talk to this same API surface on the same domain.

Need the product model behind the endpoints? What is a User?, What is a Team?, What is a Note?, and What is a Trackable? explain the objects these routes operate on.

Base URL and conventions

Base URL

All examples on this page assume the current production host:

https://locationnotes.com

Format

Requests and responses use JSON. GUIDs are the primary identifiers across notes, categories, teams, and devices.

Authorization

Private, sync, and team endpoints require an `Authorization: Bearer <access token>` header unless the route is marked public below.

Language

Public and management reads can pass `contentLanguage=en-US` or another supported content language to filter the returned note/category content.

Authentication and bearer tokens

Website sign-in pages and external-provider setup are documented on the Authentication page. API clients that want a bearer token should use the identity API login route with cookies turned off, which is the same pattern the Android app uses.

POST /api/auth/login?useCookies=false&useSessionCookies=false Anonymous

Request body

{
  "email": "tester@example.com",
  "password": "StrongP@ssw0rd!"
}

Response excerpt

{
  "tokenType": "Bearer",
  "accessToken": "eyJhbGciOi..."
}
curl -X POST "https://locationnotes.com/api/auth/login?useCookies=false&useSessionCookies=false" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "tester@example.com",
    "password": "StrongP@ssw0rd!"
  }'

Other default identity endpoints still live under /api/auth. If you need the full interactive website flow, provider callbacks, or account-linking behavior, use the Authentication page.

Public notes in map bounds

Use this endpoint for public map windows and browse-by-area experiences. This is the route the homepage map uses.

GET /api/notes/public/bounds Anonymous
  • Required query string: minLatitude, minLongitude, maxLatitude, maxLongitude
  • Optional query string: contentLanguage
curl "https://locationnotes.com/api/notes/public/bounds?minLatitude=41.78&minLongitude=-87.75&maxLatitude=41.96&maxLongitude=-87.54&contentLanguage=en-US"
[
  {
    "noteId": "4d6c5df3-3c53-4d0e-8e72-7d98a0f8a9f3",
    "ownerUserId": "a7cfd28f-c17f-4cf7-8913-47fa10fd0d1f",
    "categoryId": "4de6bb76-f25d-4c73-b8e3-81b9ca3bf08f",
    "title": "Dock gate closed",
    "body": "Security redirected vehicles to the south entrance.",
    "contentLanguage": "en-US",
    "latitude": 41.8818,
    "longitude": -87.6231,
    "visibility": "Public",
    "isDeleted": false,
    "updatedUtc": "2026-03-13T20:10:00Z",
    "clientMutationId": "web-demo-public-1",
    "teamId": null
  }
]

Public notes near a point

Use this endpoint when the client already knows a center point and wants a distance-limited result set instead of a rectangular map window.

GET /api/notes/public/nearby Anonymous
  • Required query string: latitude, longitude
  • Optional query string: radiusKm (default 5), contentLanguage
curl "https://locationnotes.com/api/notes/public/nearby?latitude=41.8818&longitude=-87.6231&radiusKm=8&contentLanguage=en-US"

Offline sync cycle

Sync clients should push local dirty data first, then pull server changes for the user, active teams, and public notes in the current area. Both sync routes require bearer auth.

POST /api/sync/push Bearer
{
  "deviceId": "5dd06ca7-34a5-4f2e-812d-3f1ef3e48290",
  "notes": [
    {
      "noteId": "ef90c4ca-88a9-4a9b-b3a8-36e2649c5dcb",
      "ownerUserId": "a7cfd28f-c17f-4cf7-8913-47fa10fd0d1f",
      "categoryId": "4de6bb76-f25d-4c73-b8e3-81b9ca3bf08f",
      "title": "Offline inspection",
      "body": "Saved while disconnected.",
      "contentLanguage": "en-US",
      "latitude": null,
      "longitude": null,
      "visibility": "Private",
      "isDeleted": false,
      "updatedUtc": "2026-03-13T20:12:00Z",
      "clientMutationId": "android-offline-42",
      "teamId": null
    }
  ],
  "categories": []
}
{
  "appliedNoteIds": [
    "ef90c4ca-88a9-4a9b-b3a8-36e2649c5dcb"
  ],
  "appliedCategoryIds": [],
  "conflicts": []
}
POST /api/sync/pull Bearer
{
  "lastSyncUtc": "2026-03-13T19:45:00Z",
  "publicArea": {
    "minLatitude": 41.78,
    "minLongitude": -87.75,
    "maxLatitude": 41.96,
    "maxLongitude": -87.54
  }
}
{
  "serverSyncUtc": "2026-03-13T20:13:02Z",
  "userNotes": [],
  "userCategories": [],
  "teamCategories": [],
  "publicNotes": [],
  "teamNotes": []
}

Sync endpoints reject anonymous requests. If the server blocks an older Android beta build, check GET /api/system/status for the minimum compatible version and beta-page URL.

Personal notes and categories

These routes back the signed-in website workspace and personal Android sync state. All require bearer auth.

Read personal notes

GET /api/notes/mine Bearer

Optional query string: contentLanguage and full map bounds with minLatitude, minLongitude, maxLatitude, maxLongitude.

Create a note

POST /api/notes/mine Bearer
{
  "categoryId": "4de6bb76-f25d-4c73-b8e3-81b9ca3bf08f",
  "title": "Category-only feedback",
  "body": "This note stays off the map on purpose.",
  "contentLanguage": "en-US",
  "latitude": null,
  "longitude": null,
  "visibility": "Public",
  "commentPolicy": "LoggedInUsers"
}

Note page reads and actions

GET /api/public/notes/{noteId} Anonymous or Bearer
GET /api/public/notes/{noteId}/comments Anonymous or Bearer
POST /api/public/notes/{noteId}/comments Bearer
GET /api/public/notes/{noteId}/trackables Anonymous or Bearer
POST /api/public/notes/{noteId}/trackables Bearer
{
  "body": "I found it too."
}
{
  "trackableSecretCodes": "LN4C8R2Z",
  "selectedActiveTrackableIds": [
    "f3a8f841-20db-4f1e-a3f8-9f14bc0b3c31"
  ],
  "activeTrackableAttachMode": "Self"
}

These note-page routes respect private-note access. If the caller can open the note page, the API can return comments and visible trackables for that note. Attaching trackables requires the note owner or a team admin.

Read categories

GET /api/categories/mine Bearer
GET /api/categories/mine/tree Bearer

Create or move categories

POST /api/categories/mine Bearer
POST /api/categories/mine/{categoryId}/move Bearer
{
  "name": "Field Reports",
  "contentLanguage": "en-US",
  "parentCategoryId": null
}

Team endpoints

Team routes cover membership, invites, settings, team notes, and team categories. All require bearer auth and the server enforces admin/member permissions per route.

Common reads

  • GET /api/teams
  • GET /api/teams/{teamId}/notes
  • GET /api/teams/{teamId}/categories
  • GET /api/teams/{teamId}/categories/tree
  • GET /api/teams/{teamId}/invite-links

Create a team

{
  "name": "beta-testers",
  "title": "Beta Testers",
  "description": "Public beta release gate for Android validation.",
  "joinPolicy": "RequestsAllowed",
  "pageVisibility": "Public",
  "defaultNoteVisibility": "Public",
  "contentLanguage": "en-US"
}

Create a team note

POST /api/teams/{teamId}/notes Bearer
{
  "categoryId": "68cb4c9b-d9bd-4bde-8c6a-a03a4c70b283",
  "title": "Shared dock inspection",
  "body": "Visible to the team until we publish it.",
  "contentLanguage": "en-US",
  "latitude": 41.8818,
  "longitude": -87.6231,
  "visibility": "Private",
  "commentPolicy": "TeamMembers"
}

Membership and invite operations

  • POST /api/teams/{teamId}/memberships/request
  • POST /api/teams/{teamId}/memberships/invite
  • POST /api/teams/{teamId}/memberships/{membershipId}/accept
  • POST /api/teams/{teamId}/memberships/{membershipId}/refuse
  • POST /api/teams/{teamId}/memberships/{membershipId}/approve
  • POST /api/teams/{teamId}/memberships/{membershipId}/deny
  • POST /api/teams/{teamId}/memberships/{membershipId}/promote-admin
  • DELETE /api/teams/{teamId}/memberships/{membershipId}
  • POST /api/teams/invite-links/{teamSlug}/{inviteCode}/join

Trackable endpoints

Trackables support public-code browsing, secret-code or private scan-QR activation, one-time secret reveal during creation, browser-side active sessions, comments, and grouped inventory workflows. Secret short codes and private scan URLs are only returned from the create endpoints below. They are never returned again from read, lookup, comment, or detail routes.

Important trackable rules:
  • Public codes are short public tokens that stay globally unique across all trackables.
  • Public codes and short secret codes are collision-free against each other, so one short code cannot mean both things.
  • Short secret codes and private scan URLs are possession-based access credentials.
  • Website note-attachment forms accept one trackable code at a time and only attach from an existing short secret code match.
  • If an external code is not registered yet, create the trackable first. Manual note-attachment entry does not auto-register new third-party codes.
  • Activation is inferred from OwnerUserId; there is no separate persisted activation flag.
  • Unactivated trackables cannot be attached to notes and cannot accept comments.
  • A trackable can belong to only one group at a time, and changing groups requires detach first, then reattach.

Identifier formats and examples

Format Example Lookup rule
Public code token LN-7K4V9T Globally unique short public token. Safe for public lookup and public links.
Public entry route GET /trackable/LN-7K4V9T Short public entry URL. The website can redirect this to the canonical public page.
Secret entry route GET /trackable/LN4C8R2Z Short possession-based entry URL for someone holding the item.
User canonical route GET /api/trackables/profile/alex/LN-7K4V9T Canonical API route for a user-owned public trackable.
Team canonical route GET /api/trackables/team/beta-testers/LN-7K4V9T Canonical API route for a team-owned public trackable.
System short secret code LN4C8R2Z Globally unique across all trackables and also unique against public codes. Intended for manual possession-based entry.
Alternate system prefix GT8M2Q7V Same generated short-secret pattern on sister deployments that use the GT prefix.
Bring-your-own secret code ITEM42X Advanced external identifier. Must stay unique across all trackables and must not start with LN or GT.
Private scan route https://locationnotes.com/trackable/AB4D5QW2...<100 chars total>... Unique across all trackables and intended to come from QR scanning instead of manual entry.

Generated public-code tokens and generated short-secret bodies use the same smudge-resistant character family. Typed lookup treats O like 0, I or L like 1, S like 5, and U like V. For system short-secret codes, that normalization applies to the generated body after the literal prefix. Public codes use that same configured prefix with a dash before the generated body, so they are visually distinct from short secret codes.

List and detail reads

  • GET /api/trackables/public
  • GET /api/trackables/mine
  • GET /api/trackables/{trackableId}
  • GET /api/trackables/profile/{userName}/{publicCode}
  • GET /api/trackables/team/{teamSlug}/{publicCode}
  • GET /api/trackables/{trackableId}/journey

Create one trackable

POST /api/trackables Bearer
{
  "name": "Promo coin",
  "description": "Launch event inventory item",
  "teamId": null,
  "isPublic": true,
  "activateImmediately": false,
  "secretCode": ""
}
{
  "trackableId": "f3a8f841-20db-4f1e-a3f8-9f14bc0b3c31",
  "heading": "Unactivated Trackable",
  "description": "Please activate this trackable item",
  "items": [
    {
      "trackableId": "f3a8f841-20db-4f1e-a3f8-9f14bc0b3c31",
      "name": "Unactivated Trackable",
      "publicCode": "LN-7K4V9T",
      "secretCode": "LN4C8R2Z",
      "scanUrl": "https://locationnotes.com/trackable/ABCD...<100 characters total>...",
      "qrPayload": "https://locationnotes.com/trackable/ABCD...<100 characters total>..."
    }
  ]
}

Bring your own secret code

{
  "name": "Marketing token",
  "description": "Bring-your-own identifier",
  "teamId": null,
  "isPublic": false,
  "activateImmediately": true,
  "secretCode": "TAG42"
}

The server accepts caller-supplied secret codes that do not start with LN or GT and do not match any existing secret or public code. The returned public code is still system-generated.

Create a group

POST /api/trackables/groups Bearer
{
  "name": "Gnomes in this hunt",
  "description": "Seasonal hunt inventory",
  "defaultTrackableTitle": "",
  "defaultTrackableDescription": "",
  "trackableCount": 12,
  "teamId": null,
  "isPublic": true,
  "activateImmediately": false
}

Groups are limited to 100 trackables. Unactivated items fall back to the group defaults until activation. Once activated, they must use their own title and description.

Lookup and active session

  • POST /api/trackables/lookup matches a public code token or short secret code after normalizing the common smudged-character substitutions listed above.
  • GET /trackable/{code} is the short website entry route. It accepts a public code, a short secret code, or the long QR token and then routes to the correct flow.
  • GET /api/trackables/active lists the client's currently active secret-code sessions.
  • GET /api/trackables/active/{trackableId} returns the active landing payload, including activation requirements and grouped items when available.
  • POST /api/trackables/active/{trackableId}/message updates the remembered status text.
  • DELETE /api/trackables/active/{trackableId} ends the remembered secret-code session on that client.

Secret-code lookups and private scan-QR visits create an active client session instead of returning the secret value. That active session is what lets later note flows attach the trackable without re-entering the code.

A bare public-code lookup is safe because short public codes are globally unique. The website also accepts the short direct route forms /trackable/{publicCode}, /trackable/{secretCode}, and /trackable/{qrToken}.

That distinction also applies on note pages: a public code is for opening the public trackable page, while note attachment expects an existing short secret code or an already-active browser session.

GET /api/trackables/lookup?code=LN4C8R2Z
{
  "found": true,
  "trackableId": "f3a8f841-20db-4f1e-a3f8-9f14bc0b3c31",
  "isPublicCodeMatch": false,
  "usesSecretAccess": true,
  "redirectUrl": "/en-US/trackables/active/f3a8f841-20db-4f1e-a3f8-9f14bc0b3c31"
}
GET /trackable/LN-7K4V9T
GET /api/trackables/profile/alex/LN-7K4V9T
GET /api/trackables/team/beta-testers/LN-7K4V9T
GET /trackable/ABCD...<100 characters total>...

Activate to self or team

POST /api/trackables/{trackableId}/activate Bearer
{
  "name": "Bramble the Mossy Wanderer",
  "description": "A small green gnome ready for the next stop.",
  "teamId": "optional-team-guid"
}

If teamId is supplied, the trackable becomes team-owned, team admins can manage it, and the activating member keeps control while they remain on that team. Without teamId, the trackable becomes personally owned.

For group-created unactivated items, activation is also the step where the new owner must choose the final item-specific title and description.

Detach and reattach groups

  • DELETE /api/trackables/{trackableId}/group removes the current group association.
  • POST /api/trackables/{trackableId}/group with { "trackableGroupId": "..." } associates a detached trackable with a new group.

A trackable can only belong to one group at a time. The server enforces the detach-first rule. Only the original activator can reattach a detached trackable, and the destination group must also be controlled by that user or an eligible team admin.

{
  "trackableGroupId": "4bdffcab-bb51-4fd8-8c15-59f7b2d72c3f"
}

Trackable comments

  • GET /api/trackables/{trackableId}/comments
  • POST /api/trackables/{trackableId}/comments

Clients must be logged in to post comments. Unactivated trackables cannot be commented on.

{
  "body": "Starting the route now."
}

Visibility and journey disclosure

Trackable journey pages may show mapped locations even when some underlying notes are private. In those cases, unauthorized viewers can receive the location point but not the protected note content.

Authorized viewers receive the note title, description, and note link directly from the preloaded journey payload and map pin popups.

Deletion and retention

Deleting one account does not automatically delete every trackable that account ever touched. Shared or team-owned trackables can remain while only the deleted user's removable personal activity is removed.

Review the Delete Data page and the account-delete API section for the current retention boundaries.

System and beta metadata

GET /api/system/status Anonymous

Use this for health checks, Android compatibility gating, and the current beta-page URL.

GET /api/system/beta-android Anonymous

Returns staged Android beta metadata, including display version, version code, minimum compatible version, and release notes.

curl "https://locationnotes.com/api/system/status"
{
  "status": "online",
  "utcNow": "2026-03-13T21:15:00Z",
  "androidMinimumCompatibleDisplayVersion": "1.0.0-beta.20260313.2",
  "androidMinimumCompatibleVersionCode": "2026031302",
  "androidCompatibilityMessage": "The API changed after older beta builds were published. Update to the latest beta before syncing or using live team management.",
  "androidBetaPageUrl": "https://locationnotes.com/uk-UA/account/beta"
}

Delete account from the API

Authenticated clients can permanently delete the current account and synced personal data through the API. Read the Delete Data page first if you need the full retention rules for team-owned data and exports.

Trackable cleanup is not all-or-nothing. The server removes the deleted user's personal trackable activity where it can, but it does not remove shared trackables, team-owned trackables, or other users' history just because one account is deleted. If a team-owned trackable still matters to the team, it remains with that team. If another person's activity is still attached to a trackable, that trackable stays in the system.

DELETE /api/account Bearer
curl -X DELETE "https://locationnotes.com/api/account" \
  -H "Authorization: Bearer <access token>"
{
  "deletedAccount": true,
  "notesDeleted": 14,
  "categoriesDeleted": 6,
  "linkedProvidersDeleted": 2
}

Errors and problem details

Validation, permission, and lookup failures return standard HTTP status codes. Many workspace routes return RFC 7807 style problem-details JSON with a title, detail, and status.

{
  "title": "Request rejected.",
  "detail": "Map bounds require minLatitude, minLongitude, maxLatitude, and maxLongitude together.",
  "status": 400
}
  • 400 for validation or business-rule rejection
  • 401 for missing or invalid authentication
  • 403 for authenticated users who do not have access
  • 404 for missing notes, categories, teams, memberships, or invite links