Base URL and conventions
Base URL
All examples on this page assume the current production host:
https://www.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. Register first when the caller does not already have a LocationNotes account.
API clients that want a bearer token should then use the identity API login route with cookies turned off, which is the same pattern the Android app uses.
POST /api/auth/register
Anonymous
Register request body
{
"email": "tester@example.com",
"password": "StrongP@ssw0rd!"
}
Register behavior
A successful register call creates the account but does not replace login. Call the bearer login route next when the client needs an access token for private, sync, team, or manage routes.
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://www.locationnotes.com/api/auth/login?useCookies=false&useSessionCookies=false" \
-H "Content-Type: application/json" \
-d '{
"email": "tester@example.com",
"password": "StrongP@ssw0rd!"
}'
The complete route inventory below also lists the current framework-managed identity routes under /api/auth.
If you need the full interactive website flow, provider callbacks, account-security pages, or provider-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
- Responses are capped by the site's public-data exposure limit, which is currently 500 by default, and ordered by recent activity first.
These public map reads include normal Public notes and notes using VisibleOnceAssociatedTrackableAccessed only while they still have no associated trackables. Once a trackable is attached, the note drops out of anonymous public-map discovery until the viewer has unlocked one of the associated trackables on a page-level route.
curl "https://www.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
- Nearby results are capped by the site's public-data exposure limit, which is currently 500 by default.
The same visibility rule applies here as the bounds endpoint: Public notes are included normally, and VisibleOnceAssociatedTrackableAccessed notes are included only until they gain their first associated trackable.
curl "https://www.locationnotes.com/api/notes/public/nearby?latitude=41.8818&longitude=-87.6231&radiusKm=8&contentLanguage=en-US"
Public profile and team page note streams
These routes back the published map and note strip on public profile and public team pages. They use the same note-access rules as the page itself and accept either a nearby center point or full map bounds.
GET /api/public/profiles/{userName}/notes/nearby
Anonymous or Bearer
GET /api/public/teams/{teamName}/notes/nearby
Anonymous or Bearer
Required query string: latitude, longitude or full map bounds with minLatitude, minLongitude, maxLatitude, maxLongitude.
Optional query string: radiusKm (default 5), contentLanguage, includeAllLanguages
The website normally sends the current route language as contentLanguage so /en-US and /tlh public pages do not mix authored note languages in the same map or strip by default.
Set includeAllLanguages=true only when the client is intentionally building a multilingual browse surface instead of mirroring the website page.
curl "https://www.locationnotes.com/api/public/profiles/michael-kappel/notes/nearby?minLatitude=41.87&minLongitude=-87.64&maxLatitude=41.89&maxLongitude=-87.62&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",
"externalLinkUrl": "https://example.com/offline-inspection",
"externalLinkDescription": "",
"applyExternalLinkChanges": true,
"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.
Note read payloads now include lastActivityUtc so clients can show the most recent activity separately from persistence/update timestamps.
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",
"externalLinkUrl": "https://example.com/field-guide",
"externalLinkDescription": "",
"latitude": null,
"longitude": null,
"visibility": "VisibleOnceAssociatedTrackableAccessed",
"commentPolicy": "LoggedInUsers"
}
visibility accepts Private, Public, and VisibleOnceAssociatedTrackableAccessed. The associated-trackable mode behaves like Public until the note has one or more associated trackables. After that, public discovery and note-page access require that the viewer has already unlocked one of those associated trackables.
- No associated trackables yet: the note behaves like a public note.
- After the first association: the note drops out of anonymous public discovery and normal public note-page access.
- Unlocked viewer: someone who already unlocked one associated trackable can open the note page and use the connected public note-page API routes.
- Owner or authorized team member: keeps normal note access and management rights.
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 note access rules, including private notes and notes gated by associated-trackable 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.
- Before the first associated trackable exists, these routes behave like normal public note-page routes.
- After at least one association exists, callers without access to one associated trackable should expect 404 from the public note-page routes even though the note still exists.
- The same note can still return 200 for the owner, authorized team members, or viewers who already unlocked one associated trackable.
Read categories
GET /api/categories/mine
Bearer
GET /api/categories/mine/tree
Bearer
Workspace tree helpers
GET /api/categories/mine/tree/sections
Bearer
GET /api/categories/mine/tree/children?parentCategoryId={categoryId}
Bearer
Use these lighter helper routes for lazy-loaded category pickers and sidebar trees. tree/sections groups root categories by contentLanguage. tree/children returns the direct child nodes for one parent and includes childCategoryCount so the client can decide whether to show an expand affordance before loading grandchildren.
[
{
"contentLanguage": "en-US",
"rootCategories": [
{
"categoryId": "68cb4c9b-d9bd-4bde-8c6a-a03a4c70b283",
"name": "Field Reports",
"contentLanguage": "en-US",
"parentCategoryId": null,
"childCategoryCount": 2
}
]
}
]
[
{
"categoryId": "c4b2e65f-5cf2-4ab6-8577-a89dfb51407f",
"name": "Dock checks",
"contentLanguage": "en-US",
"parentCategoryId": "68cb4c9b-d9bd-4bde-8c6a-a03a4c70b283",
"childCategoryCount": 0
}
]
Create or move categories
POST /api/categories/mine
Bearer
POST /api/categories/mine/{categoryId}/move
Bearer
{
"name": "Field Reports",
"contentLanguage": "en-US",
"parentCategoryId": null
}
Personal GPX waypoint handoff
GET /api/notes/mine/gpx
Bearer
GET /api/notes/mine/gpx exports only mapped personal notes with saved coordinates as GPX 1.1 waypoints.
POST /api/notes/mine/gpx
Bearer
POST /api/notes/mine/gpx accepts multipart form data with file, visibility, and optional contentLanguage. The import reads waypoint entries only, creates normal mapped notes, and never overwrites an existing note.
Duplicate checks stay inside the selected personal scope. Matching title plus coordinates with the same note text are reported as duplicates. Matching title plus coordinates with different note text are skipped to avoid overwrite.
curl "https://www.locationnotes.com/api/notes/mine/gpx" \
-H "Authorization: Bearer <access token>"
curl -X POST "https://www.locationnotes.com/api/notes/mine/gpx" \
-H "Authorization: Bearer <access token>" \
-F "file=@waypoints.gpx;type=application/gpx+xml" \
-F "visibility=Private" \
-F "contentLanguage=en-US"
What is Import and Export?
covers how GPX waypoint exchange differs from the full JSON and portable ZIP account exports.
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
Team and team-note read models include lastActivityUtc. Public trimming prefers the most recently active records first.
Team category tree helpers
GET /api/teams/{teamId}/categories/tree/sections
Bearer
GET /api/teams/{teamId}/categories/tree/children?parentCategoryId={categoryId}
Bearer
These routes use the same section and child-node shapes as the personal workspace tree helpers, but the data stays inside the selected team scope and still requires active team membership.
Use DELETE /api/teams/{teamId}/notes/{noteId} when the note should leave the team workspace but continue existing for the owner, and use DELETE /api/teams/{teamId}/notes/{noteId}/delete when the team note itself should be permanently deleted.
Create a team
{
"name": "Beta Testers",
"title": "Beta Testers",
"description": "Public beta release gate for Android validation.",
"externalLinkUrl": "https://example.com/beta-program",
"externalLinkDescription": "",
"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",
"externalLinkUrl": "https://example.com/shared-briefing",
"externalLinkDescription": "",
"latitude": 41.8818,
"longitude": -87.6231,
"visibility": "Private",
"commentPolicy": "TeamMembers"
}
Team GPX waypoint handoff
GET /api/teams/{teamId}/notes/gpx
Bearer
POST /api/teams/{teamId}/notes/gpx
Bearer
GET /api/teams/{teamId}/notes/gpx and POST /api/teams/{teamId}/notes/gpx apply the same GPX rules inside the selected team scope. Duplicate and overwrite checks only compare against that team's notes.
curl "https://www.locationnotes.com/api/teams/{teamId}/notes/gpx" \
-H "Authorization: Bearer <access token>"
curl -X POST "https://www.locationnotes.com/api/teams/{teamId}/notes/gpx" \
-H "Authorization: Bearer <access token>" \
-F "file=@team-waypoints.gpx;type=application/gpx+xml" \
-F "visibility=Public" \
-F "contentLanguage=en-US"
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
{
"isSingleUse": false
}
{
"inviteLinkId": "9a4726dc-4fb1-4c16-b7b3-5d48ea68fdce",
"teamId": "0f4ddf5d-f3dc-4417-b96d-8e212d24235e",
"teamName": "Invite Team",
"teamSlug": "invite-team",
"code": "8K4V9T",
"createdByDisplayName": "invite-admin",
"createdByCurrentUser": true,
"inviterIsCurrentlyAdmin": true,
"isSingleUse": false,
"createdUtc": "2026-03-18T21:15:00Z"
}
{
"teamSlug": "invite-team",
"membershipStatus": "RequestingMembership"
}
Trackable endpoints
Trackables support public-code browsing, secret-code or scan-only QR access, one-time secret reveal during creation, browser-side active sessions, comments, and grouped inventory workflows.
Secret short codes and scan-only QR 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 scan-only QR URLs are possession-based access credentials.
- Think of trackables and trackable groups as Visible Once Accessed or Always Visible To Everyone. Signed-in secret access can be saved to the account for later devices.
- 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 share URL. The same base route is used for public codes, short secret codes, and long scan-only QR tokens. |
| Localized public trackable page |
GET /en-US/trackables/LN-7K4V9T |
Language-specific rendered public page reached after the share URL resolves the code. |
| Secret entry route |
GET /trackable/LN4C8R2Z |
Short possession-based entry URL for someone holding the item. It uses the same <code>/trackable/{code}</code> base route as the public and long scan forms. |
| 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 <code>GT</code> prefix. |
| Bring-your-own secret code |
ITEM42X |
Advanced external identifier. Must stay unique across all trackables and must not start with <code>LN</code> or <code>GT</code>. |
| 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
Public list reads are capped by the site's public-data exposure limit, which is currently 500 by default.
Trackable and trackable-group read models include lastActivityUtc so dashboards and API clients can show recent activity explicitly.
GET /api/trackables/public
GET /api/trackables/mine
GET /api/trackables/{trackableId}
POST /api/trackables/{trackableId}/watch
DELETE /api/trackables/{trackableId}/watch
GET /api/trackables/{trackableId}/journey
POST /api/trackables/{trackableId}/journey-stops
DELETE /api/trackables/{trackableId}/journey-stops/{journeyStopId}
GET /api/trackables/public and the website's public trackable browse pages stay multilingual so public journey and logistics data are not hidden by route language.
Switch the website language when you want localized chrome around the same public trackable data.
Create one trackable
POST /api/trackables
Bearer
{
"name": "Promo coin",
"description": "Launch event inventory item",
"externalLinkUrl": "https://example.com/promo-coin",
"externalLinkDescription": "",
"teamId": null,
"visibility": "AlwaysVisibleToEveryone",
"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": "ABCD...<100 characters total>..."
}
]
}
Bring your own secret code
{
"name": "Marketing token",
"description": "Bring-your-own identifier",
"teamId": null,
"visibility": "VisibleOnceAccessed",
"activateImmediately": true,
"secretCode": "TAG42"
}
The server accepts caller-supplied secret codes that do not start with LN, GT, or GC 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": "",
"defaultExternalLinkUrl": "https://example.com/hunt-rules",
"defaultExternalLinkDescription": "",
"trackableCount": 12,
"teamId": null,
"visibility": "AlwaysVisibleToEveryone",
"activateImmediately": false,
"watchGroupActivityWhenCreated": true
}
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.
Trackable-group creation can also accept watchGroupActivityWhenCreated when an unactivated batch should start on the creator's watch list.
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 shown to users and API clients. 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 current active secret-code session when one exists.
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 scan-only 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.
When the caller is authenticated, the same Visible Once Accessed lookup also links the trackable to that account so later signed-in devices can reopen the trackable, its group page, and normal follow-up routes without re-entering the secret.
The website intentionally uses the same direct entry base route for all three forms: /trackable/{publicCode}, /trackable/{secretCode}, and /trackable/{qrToken}.
The localized /{lang}/trackable/{code} route is the rendered single-item landing after automatic language selection, but it still should not be shown as the shareable or printed URL.
After a public-code lookup resolves, the rendered public page lives at /{lang}/trackable/{publicCode}.
That shared entry route stays valid even if the configured short-code lengths change later, because the server resolves the code value instead of depending on hard-coded website path variations.
Owner-scoped website trackable routes are intentionally not part of the public URL system.
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 /en-US/trackables/LN-7K4V9T
GET /trackable/ABCD...<100 characters total>...
Activate to self or team
POST /api/trackables/{trackableId}/activate
Bearer
{
"name": "",
"description": "",
"useGroupDefaultTitle": true,
"useGroupDefaultDescription": true,
"externalLinkUrl": "",
"externalLinkDescription": "",
"useGroupDefaultExternalLink": true,
"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.
When a grouped item is activated, the owner can either save an explicit item title and description or intentionally copy the group's current default title and description into the item.
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.
POST /api/trackables/groups/{trackableGroupId}/watch starts monitoring the group's visible members. Existing member-item watches from that same group can still collapse into the group watch, and owner-or-watch overlap is deduplicated per user.
DELETE /api/trackables/groups/{trackableGroupId}/watch stops monitoring the 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
PUT /api/trackables/{trackableId}/comments/{commentId}
DELETE /api/trackables/{trackableId}/comments/{commentId}
Unactivated trackables cannot receive comments or journey stops.
Authenticated users can post comments directly. Anonymous callers can also post, but every anonymous write must come from that browser's active trackable session or resend the exact short secret code or private QR token for that specific trackable.
Only the signed-in comment author can edit their own comment. Trackable owners and current team admins can delete comments or journey stops, but they still cannot rewrite somebody else's words.
{
"body": "Starting the route now.",
"accessCode": "LN4C8R2Z"
}
Direct journey stops
The journey feed is now an immutable stop history. Note-backed stops snapshot the location when the note is attached, and direct map reports can be saved without creating a note first.
If a linked note later moves to a different coordinate, the trackable journey still keeps the original snapped stop so logistics history does not silently rewrite itself.
Anonymous direct reports follow the same rule as anonymous comments: the caller must use that browser's active trackable session or resend the short secret code or private QR token on the write request.
{
"latitude": 41.8819,
"longitude": -87.6278,
"accessCode": "LN4C8R2Z"
}
GET /api/trackables/{trackableId}/journey returns label-first place facts. Each point carries coordinateId, locationLabel, and currentNotesAtCoordinate so clients can show the current visible notes at that coordinate without treating the stop as if it owns one saved note.
Journey responses do not split the outward place contract into city, stateOrProvince, and country fields anymore. Read locationLabel plus the coordinates instead.
[
{
"journeyStopId": "8a274ad6-5dd4-45c4-969a-c13cc1b8d92c",
"coordinateId": "565c42dd-2e12-49b4-a16b-c89ff4502b8e",
"latitude": 41.8819,
"longitude": -87.6278,
"associatedUtc": "2026-04-05T18:30:00Z",
"isLocationOnly": false,
"isAnonymous": false,
"canDelete": false,
"canConvertToNote": false,
"locationLabel": "Chicago, Illinois, United States",
"creatorDisplayText": "Jordan",
"creatorProfileUserName": "jordan",
"currentNotesAtCoordinate": [
{
"noteId": "4d6c5df3-3c53-4d0e-8e72-7d98a0f8a9f3",
"title": "Lobby drop",
"contentLanguage": "en-US",
"thumbnailUrl": "/api/images/notes/4d6c5df3-3c53-4d0e-8e72-7d98a0f8a9f3/thumbnail"
}
]
}
]
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.
Trackable flow guides
The trackable API has both a full signed-in ownership workflow and a lighter anonymous secret-backed reporting workflow.
Use the dedicated pages below when you need ordered call sequences, decision points, and copy-paste examples instead of a route catalog.
Error lookup
Trackable problem-details responses now include a stable machine-readable code field. Use the error reference page to map failures to likely causes and fixes.
Open the trackable API error reference
External-link API support
External-link support is available on note, team, trackable, trackable-group, and sync APIs. Clients can call the verify endpoint first, but create and update routes still revalidate the link server-side before saving.
Verify a link
POST /api/external-links/verify
Bearer
Call this before showing the description field in a custom client. A successful response returns a normalized URL, the suggested description from the page title, and a support URL for review issues. Failed responses return HTTP 400 with a detail message, a support URL, and an upstreamStatusCode when the destination site itself returned an HTTP error.
{
"url": "https://example.com/hunt-rules"
}
{
"normalizedUrl": "https://example.com/hunt-rules",
"suggestedDescription": "Example Domain",
"supportUrl": "/en-US/support"
}
{
"title": "Request rejected.",
"status": 400,
"detail": "The external page returned HTTP 405 Method Not Allowed during verification.",
"supportUrl": "/en-US/support",
"upstreamStatusCode": 405
}
Note, team, and sync fields
Create and update note, team, and sync payloads use externalLinkUrl plus externalLinkDescription. When ApplyExternalLinkChanges is true on sync note payloads, the server revalidates the link before saving.
CreateNoteRequest.ExternalLinkUrl
CreateNoteRequest.ExternalLinkDescription
CreateTeamRequest.ExternalLinkUrl
CreateTeamRequest.ExternalLinkDescription
UpdateTeamSettingsRequest.ExternalLinkUrl
UpdateTeamSettingsRequest.ExternalLinkDescription
NoteDto.ExternalLinkUrl
NoteDto.ExternalLinkDescription
NoteDto.ApplyExternalLinkChanges
Trackable and group fields
Trackable creation and update payloads use externalLinkUrl, externalLinkDescription, and externalLinkAppendPublicTrackableCode. Trackable-group creation and update use defaultExternalLinkUrl, defaultExternalLinkDescription, and defaultExternalLinkAppendPublicTrackableCode. Activation still accepts useGroupDefaultStatusMessage alongside useGroupDefaultTitle, useGroupDefaultDescription, and useGroupDefaultExternalLink when the owner intentionally wants to start from the group's defaults.
TrackableGroupCreateInputModel.Name accepts letters, numbers, spaces, and capital letters in requests. The server stores the page slug separately in lowercase with underscores and keeps that slug fixed after creation.
When a stored trackable or trackable-group default link opts in, LocationNotes appends LN={publicCode} only when the click starts from a specific trackable page. Group pages still use the stored URL by itself because no individual trackable code is in context there.
Activation can also accept optional initialJourneyStopLatitude and initialJourneyStopLongitude values when the first tracked location should be created in the same request.
TrackableCreateInputModel.ExternalLinkUrl
TrackableCreateInputModel.ExternalLinkDescription
TrackableCreateInputModel.ExternalLinkAppendPublicTrackableCode
TrackableActivationInputModel.StatusMessage
TrackableGroupCreateInputModel.DefaultExternalLinkUrl
TrackableGroupCreateInputModel.DefaultExternalLinkDescription
TrackableGroupCreateInputModel.DefaultExternalLinkAppendPublicTrackableCode
TrackableGroupCreateInputModel.DefaultStatusMessage
TrackableGroupCreateInputModel.WatchGroupActivityWhenCreated
TrackableActivationInputModel.UseGroupDefaultTitle
TrackableActivationInputModel.UseGroupDefaultDescription
TrackableActivationInputModel.UseGroupDefaultStatusMessage
TrackableActivationInputModel.ExternalLinkUrl
TrackableActivationInputModel.ExternalLinkDescription
TrackableActivationInputModel.UseGroupDefaultExternalLink
TrackableActivationInputModel.InitialJourneyStopLatitude
TrackableActivationInputModel.InitialJourneyStopLongitude
Server behavior
- Clients should treat externalLinkDescription as unavailable until verification succeeds.
- If the client leaves the description blank, the server uses the verified page title or "no title found on external page".
- Rejected destinations can fail because the URL format is invalid, the host is local or private, the page could not be reached, the page did not return success, or adult-content signals were found.
- Public pages render saved external links through a LocationNotes exit page instead of sending the visitor straight to the third-party destination.
Image endpoints
Image visibility always follows the parent item. If the caller can open the connected profile, note, team, trackable, or trackable-group page, the caller can open its images too. If the parent item is not accessible, image list reads return no items and direct image downloads return 404.
Clients should also remember that uploads are screened before save, originals are not retained, and the server stores only resized JPEG variants. The website and Android app both use these same API contracts and file routes.
List images by parent
GET /api/images/profiles/{userId}
Anonymous or Bearer
GET /api/images/notes/{noteId}
Anonymous or Bearer
GET /api/images/teams/{teamId}
Anonymous or Bearer
GET /api/images/trackables/{trackableId}
Anonymous or Bearer
GET /api/images/trackable-groups/{trackableGroupId}
Anonymous or Bearer
Use these reads to populate galleries. Each item includes the image GUID, original dimensions, and the relative URLs for the thumbnail, small, medium, and large stored copies.
originalWidth and originalHeight describe the uploaded source image for reference. The actual downloadable files are still the resized stored variants linked by thumbnailUrl, smallUrl, mediumUrl, and largeUrl.
curl "https://www.locationnotes.com/api/images/notes/4d6c5df3-3c53-4d0e-8e72-7d98a0f8a9f3"
[
{
"contentImageId": "f1d52aa2-4d59-49bf-8d21-7d0b4e9e57f1",
"targetType": 1,
"noteId": "4d6c5df3-3c53-4d0e-8e72-7d98a0f8a9f3",
"originalWidth": 1600,
"originalHeight": 1200,
"createdUtc": "2026-03-18T21:15:00Z",
"thumbnailUrl": "/Images/Thumbnail/f1d52aa2-4d59-49bf-8d21-7d0b4e9e57f1-64.jpg",
"smallUrl": "/Images/Small/f1d52aa2-4d59-49bf-8d21-7d0b4e9e57f1-306.jpg",
"mediumUrl": "/Images/Medium/f1d52aa2-4d59-49bf-8d21-7d0b4e9e57f1-612.jpg",
"largeUrl": "/Images/Large/f1d52aa2-4d59-49bf-8d21-7d0b4e9e57f1-1024.jpg"
}
]
Download stored bytes
GET /api/images/{contentImageId}/{variant}
Anonymous or Bearer
Valid variant values are thumbnail, small, medium, and large. Responses return image/jpeg.
The website file URLs under /Images/{Variant}/{guid}-{size}.jpg enforce the same parent-visibility rule before image bytes are served, so direct image links are still protected by the connected profile, note, team, trackable, or trackable-group permissions.
curl -L "https://www.locationnotes.com/api/images/f1d52aa2-4d59-49bf-8d21-7d0b4e9e57f1/large" --output note-large.jpg
Upload images
POST /api/images/profiles
Bearer
POST /api/images/notes/{noteId}
Bearer
POST /api/images/teams/{teamId}
Bearer
POST /api/images/trackables/{trackableId}
Bearer
POST /api/images/trackable-groups/{trackableGroupId}
Bearer
Send one multipart file field named file. The caller must already have parent-management permission for the target, which means team-admin access for team pages and the normal edit/management permission for the other parent types. Uploads are screened before save, then stored as resized JPEG copies only.
curl -X POST "https://www.locationnotes.com/api/images/trackables/721f5205-ed2c-43e8-8ecd-1502d5bb7b56" \
-H "Authorization: Bearer <access token>" \
-F "file=@tracker.jpg"
{
"image": {
"contentImageId": "f1d52aa2-4d59-49bf-8d21-7d0b4e9e57f1",
"targetType": 3,
"trackableId": "721f5205-ed2c-43e8-8ecd-1502d5bb7b56",
"originalWidth": 1600,
"originalHeight": 1200,
"createdUtc": "2026-03-18T21:15:00Z",
"thumbnailUrl": "/Images/Thumbnail/f1d52aa2-4d59-49bf-8d21-7d0b4e9e57f1-64.jpg",
"smallUrl": "/Images/Small/f1d52aa2-4d59-49bf-8d21-7d0b4e9e57f1-306.jpg",
"mediumUrl": "/Images/Medium/f1d52aa2-4d59-49bf-8d21-7d0b4e9e57f1-612.jpg",
"largeUrl": "/Images/Large/f1d52aa2-4d59-49bf-8d21-7d0b4e9e57f1-1024.jpg"
}
}
Delete images
DELETE /api/images/{contentImageId}
Bearer
Deletion requires the same parent-management permission that upload requires. When a delete succeeds, the content-image row and all stored resized variants are removed together.
API clients must send the HTTP DELETE verb here. Opening this URL directly in a browser address bar sends GET instead, which returns 405 Method Not Allowed because this route is not a download page.
Website delete buttons use the localized website form route at /{culture}/images/{contentImageId}/delete so browser gallery deletes keep anti-forgery protection.
curl -X DELETE "https://www.locationnotes.com/api/images/f1d52aa2-4d59-49bf-8d21-7d0b4e9e57f1" \
-H "Authorization: Bearer <access token>"
What is an Image? explains the visibility, moderation, reporting, resize, export, and deletion rules behind these routes.
Compliance reporting
Use these routes for inappropriate-content reports, tracked system errors, and the same support-ticket records that back the website support-request flow.
Anonymous callers may submit reports, but only signed-in reporters can later track status updates and super-admin resolutions through the API.
Submit a report
POST /api/compliance/reports
Anonymous or Bearer
If the caller is authenticated by website cookie or bearer token, the created report is attached to that account so it appears under GET /api/compliance/reports/mine.
{
"pageType": 2,
"contentType": 5,
"pageTitle": "Reported note title",
"pageUrl": "/en-US/Note/2f4a9f80-b7db-4f4b-9d34-0c2cb8777d9a#note-page-title",
"pageReference": "2f4a9f80-b7db-4f4b-9d34-0c2cb8777d9a",
"contentLabel": "Note Page title and content",
"contentReference": "2f4a9f80-b7db-4f4b-9d34-0c2cb8777d9a:page-content",
"contentPreview": "Title: Reported note title\n\nContent: Reported note body",
"reportTitle": "Needs review",
"reportExplanation": "This title is not appropriate for a public note."
}
Track an API ticket or report an app error
POST /api/compliance/errors
Anonymous or Bearer
If an API route already failed with an unexpected 500 and returned a ticketNumber in problem-details JSON, post that ticket here instead of creating a second error log row. Include userExplanation and set trackInSupportTickets when the caller wants super-admin follow-up.
{
"ticketNumber": "ERR-1M7Q4D9K2X5R8V6N",
"userExplanation": "Android app hit this right after I tapped refresh twice.",
"trackInSupportTickets": true,
"clientContext": {
"platform": "Android",
"screen": "My Journeys",
"appVersion": "1.0.0-beta.20260318.1"
}
}
When the failure happened only inside the Android app and there is no server ticket yet, create a new error ticket with a marker that describes where the app was.
{
"pageMarkerType": "Android screen",
"pageMarker": "My Journeys",
"requestCulture": "en-US",
"requestUrl": "https://locationnotes.com/api/trackables/active/721f5205-ed2c-43e8-8ecd-1502d5bb7b56?includeHistory=true",
"httpMethod": "GET",
"responseStatusCode": 503,
"exceptionType": "Java.Lang.IllegalStateException",
"exceptionMessage": "Journey list render failed.",
"stackTrace": "at com.locationnotes.trackables.MyJourneysFragment.render(MyJourneysFragment.kt:42)",
"userExplanation": "This happened right after scanning a QR code.",
"trackInSupportTickets": true,
"clientContext": {
"platform": "Android",
"deviceModel": "Pixel 9",
"osVersion": "Android 17",
"appVersion": "1.0.0-beta.20260318.1"
}
}
{
"ticketNumber": "ERR-1M7Q4D9K2X5R8V6N",
"usedExistingTicket": false,
"explanationSaved": true,
"trackedInSupportTickets": true,
"trackedContentReportId": "d91f6e1c-b8e2-4e38-9e96-f2de0cc4f0e2"
}
Use pageMarkerType values such as URL, Android screen, or Android background task. For server-generated tickets, keep the original URL marker and send the returned ticketNumber back here so the logged API URL stays attached to the same ticket.
Reporter status reads
GET /api/compliance/reports/mine
Bearer
GET /api/compliance/reports/{contentReportId}
Bearer
These routes return only the caller's own reports unless the caller is a super-admin. Use them to show Reported, Reviewing, or Resolved plus any final resolution note for both content reports and tracked system errors.
{
"contentReportId": "3ab419ab-4b71-4d43-b52c-303d6039f01f",
"reporterUserId": "6d650c55-b235-4370-8572-e4b772cd1aea",
"pageType": 2,
"pageTypeLabel": "Note Page",
"contentType": 5,
"contentTypeLabel": "Title and content",
"status": 2,
"statusLabel": "Resolved",
"pageTitle": "Reported note title",
"pageUrl": "/en-US/Note/2f4a9f80-b7db-4f4b-9d34-0c2cb8777d9a#note-page-title",
"pageReference": "2f4a9f80-b7db-4f4b-9d34-0c2cb8777d9a",
"contentLabel": "Note Page title and content",
"contentReference": "2f4a9f80-b7db-4f4b-9d34-0c2cb8777d9a:page-content",
"contentPreview": "Title: Reported note title\n\nContent: Reported note body",
"reportTitle": "Needs review",
"reportExplanation": "This title is not appropriate for a public note.",
"resolutionText": "Reviewed and recorded for moderation follow-up.",
"reporterDisplayName": "site-compliance-reporter",
"reviewerDisplayName": "site-compliance-admin",
"createdUtc": "2026-03-18T18:00:00Z",
"reviewedUtc": "2026-03-18T18:10:00Z"
}
Super-admin review
GET /api/compliance/reports
Bearer + SuperAdmin
PUT /api/compliance/reports/{contentReportId}
Bearer + SuperAdmin
The admin list includes the report link, page metadata, offending-content metadata, reporter identity when available, and any previous review note. Updates write the current review state and visible resolution text.
{
"status": 1,
"resolutionText": "Review opened and queued for follow-up."
}
Set status to 2 and include a non-empty resolutionText when closing a report as resolved.
Enum values
pageType: 0 profile page, 1 team page, 2 note page, 3 trackable page, 4 trackable group page, 5 system error, 6 support page
contentType: 0 title, 1 description, 2 comment, 3 body, 4 bio, 5 title and content, 6 error details, 7 image, 8 support request
status: 0 reported, 1 reviewing, 2 resolved
Website page-level reports now use 5 so one report button can cover both the visible title and the visible description/body together. Comment-level reports still use the comment-specific content types.
Website general support requests create Support Page plus Support request tickets through the same COMPLIANCE.ContentReports store, even though the browser form uses the Support page instead of this raw API route.
Tracked website, API, and Android app errors are stored in LOG.Errors. When trackInSupportTickets is enabled, the linked support ticket also appears in COMPLIANCE.ContentReports so super-admin review and reporter status follow the same workflow.
System and beta metadata
GET /api/system/status
Anonymous
Use this for health checks, Android compatibility gating, the current beta-page URL, and the resolved map/privacy state for the current request.
GET /api/system/beta-android
Anonymous
Returns staged Android beta metadata, including display version, version code, minimum compatible version, and release notes.
GET /api/system/ip-location
Anonymous
Returns the current request's best-effort IP-based location for map centering and troubleshooting.
When the client IP is private, local, or otherwise untrusted, the response stays HTTP 200 and explains the failure in
failureReason
instead of throwing a transport error.
GET /api/system/coordinate-locality
Anonymous
Reverse-geocodes a latitude/longitude pair into city, state/province, and country labels for stop-planner and map-selection flows.
Invalid coordinates return a non-throwing payload with
resolved=false
and
failureReason=invalid-coordinates.
- requestedExperienceMode versus effectiveExperienceMode tells you whether the signed-in preference survived request-time privacy rules or was forced down to no_3rd_parties.
- requestedMapSource, preferredMapSource, and fallbackMapSource tell you which provider the user asked for, which provider the server wants to try first, and which provider should load next if the preferred source fails.
- thirdPartyBrowserCallsAllowed, googleMapsAllowed, and openStreetMapAllowed tell you whether this request may call browser-side providers at all.
- hostedMapsForcedByPrivacy plus hostedMapTileUrlTemplate tell you when the request was forced onto same-origin hosted tiles, which currently render through
/maps/tiles/{z}/{x}/{y}.png.
Private-network, local-network, and otherwise unresolved IP requests intentionally take the stricter path, so a localhost or office-network test can legitimately show no_3rd_parties and hosted_maps even when the signed-in preference was different.
curl "https://www.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://www.locationnotes.com/en-US/account/beta",
"requestedExperienceMode": "latest_and_greatest",
"effectiveExperienceMode": "no_3rd_parties",
"usesSavedExperienceModePreference": false,
"usesVisitorExperienceModePreferenceCookie": false,
"thirdPartyBrowserCallsAllowed": false,
"requestedMapSource": "google_maps",
"preferredMapSource": "hosted_maps",
"fallbackMapSource": "hosted_maps",
"usesSavedMapPreference": false,
"usesVisitorMapPreferenceCookie": false,
"googleMapsAllowed": false,
"googleMapsConfigured": false,
"openStreetMapAllowed": false,
"openStreetMapConfigured": true,
"hostedMapsConfigured": true,
"hostedMapsForcedByPrivacy": true,
"hostedMapTileUrlTemplate": "/maps/tiles/{z}/{x}/{y}.png"
}
curl "https://www.locationnotes.com/api/system/ip-location"
{
"resolved": true,
"clientIpAddress": "50.77.187.28",
"lookupIpAddress": "50.77.187.28",
"usedDevelopmentFallbackIp": false,
"sourceKind": "IpAddress",
"coordinateSourceProvider": "IpInfoDb",
"localitySourceProvider": "IpInfoDb",
"latitude": 41.8758,
"longitude": -87.6206,
"city": "Chicago",
"stateOrProvince": "Illinois",
"country": "United States of America",
"isApproximate": true,
"accuracyRadiusKm": null,
"failureReason": ""
}
curl "https://www.locationnotes.com/api/system/coordinate-locality?latitude=41.8818&longitude=-87.6231"
{
"resolved": true,
"sourceKind": "Coordinates",
"coordinateSourceProvider": "StopPlanner",
"localitySourceProvider": "HostedMapPostGIS",
"latitude": 41.8818,
"longitude": -87.6231,
"city": "Chicago",
"stateOrProvince": "Illinois",
"country": "United States",
"isApproximate": false,
"accuracyRadiusKm": null,
"failureReason": ""
}
Complete route inventory
This section is the exhaustive verb-and-path checklist for the current LocationNotes API surface, including the framework-managed identity routes under /api/auth. The detailed sections above explain the main workflows; this inventory is the route-level source of truth that should stay aligned with the live endpoint table.
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://www.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": "Forbidden",
"code": "trackable_access_code_required",
"detail": "Sign in, keep this trackable active on this browser, or provide this trackable's secret code or QR access code before posting comments or location reports.",
"status": 403
}
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
Trackable write routes also add a stable code property so API clients can distinguish cases like
trackable_access_code_required,
trackable_access_code_invalid,
trackable_activation_required,
and
trackable_already_activated
without parsing English text. The full mapping is documented on the
trackable error reference page.