Developer documentation

OAuth 2.0 guide for third-party apps. Endpoints use the prefix /oauth and become absolute when OAUTH2_PUBLIC_BASE_URL is set.

Full section list

Overview

Trek Point acts as an OAuth 2.0 authorization server. Send the user to the authorize URL, receive an authorization code on your redirect_uri, then exchange that code for an access token and, in some cases, a refresh token. Use the access token as a Bearer token when calling APIs for the granted scopes.

Register an application

  1. Sign in to Trek Point.
  2. Open the Developer portal and create an application.
  3. Add every redirect URI your app will use. Redirect URIs must match exactly. Use HTTPS in production.
  4. Choose public for native or browser-based apps with PKCE, or confidential for server-side apps with a client secret.
  5. Copy the client_id and, for confidential clients, the client_secret. Secrets are only shown once.

Endpoints

Authorization
GET /oauth/authorize
Token
POST /oauth/token
Revocation
POST /oauth/revoke
Example resource (profile scope)
GET /oauth/profile — send Authorization: Bearer …

Authorization code flow

  1. Generate a PKCE code_verifier and code_challenge. This is recommended for all clients and expected for public clients.
  2. Redirect the user’s browser to /oauth/authorize with the query parameters below.
  3. After consent, we redirect to your redirect_uri with code and state.
  4. POST to /oauth/token with grant_type=authorization_code, the code, the same redirect_uri, and either client authentication or the PKCE code_verifier.
  5. Store tokens securely and call APIs with Authorization: Bearer <access_token>.

Authorize request

Query parameters for GET /oauth/authorize:

Parameter Required Description
response_type Required Must be code.
client_id Required Issued when you register the app.
redirect_uri Required Must exactly match a registered redirect URI for this client.
scope Required Space-separated list. It must be a subset of the scopes your client can request (see Scopes).
state Recommended Opaque value that you verify on callback to prevent CSRF.
code_challenge With PKCE S256 challenge derived from the code_verifier.
code_challenge_method With PKCE Use S256.

Token request

POST /oauth/token with Content-Type: application/x-www-form-urlencoded.

Exchange authorization code

  • grant_type=authorization_code
  • code — the value returned to your redirect URI
  • redirect_uri — the same URI used in the authorize step
  • Confidential clients: authenticate with HTTP Basic (client_id:client_secret) or include client_id and client_secret in the body.
  • Public clients: include client_id and code_verifier.

A successful response includes JSON with access_token, token_type (typically Bearer), expires_in, and may include refresh_token.

Refresh token

POST /oauth/token with grant_type=refresh_token, refresh_token, and scope if you want to narrow the request. The requested scope must not exceed the original grant. Confidential clients authenticate the same way as above.

A successful refresh returns a new access token. If the response includes a new refresh_token, store it and use it for the next refresh.

Revocation

POST /oauth/revoke to invalidate a token when a user signs out of your app or disconnects it. Send the token value and, where supported, token_type_hint. Confidential clients authenticate the same way as at the token endpoint.

Profile API

GET /oauth/profile returns JSON when you send Authorization: Bearer <access_token> and the token includes the profile scope:

{
  "id": <user id>,
  "email": "<email>",
  "username": "<username>"
}

Public API

APIs use the prefix /api/v1 and require an OAuth access token with the right scopes. Read endpoints are owner-scoped and only return resources owned by the token’s user.

My activities
GET /api/v1/activities — scope activities_read
Activity detail (includes geometry)
GET /api/v1/activities/<id> — scope activities_read
Create activity
POST /api/v1/activities — scope activities_write
Update activity (owner only)
PATCH /api/v1/activities/<id> — scope activities_write
Delete activity (owner only)
DELETE /api/v1/activities/<id> — scope activities_write
My routes
GET /api/v1/routes — scope routes_read
Route detail (includes geometry)
GET /api/v1/routes/<id> — scope routes_read
Create route
POST /api/v1/routes — scope routes_write
Update route (owner only)
PATCH /api/v1/routes/<id> — scope routes_write
Delete route (owner only)
DELETE /api/v1/routes/<id> — scope routes_write

Read and write operations are tied to the OAuth token owner. Apps can only access and modify activities and routes created by that user account.

File format exports

You can export routes and activities by passing a format query parameter to the export endpoints.

These export endpoints are served from the Maps API host, not the OAuth /oauth prefix: GET /api/routes/<id>/export?format=... and GET /api/activities/<id>/export?format=....

Route export formats

  • format=gpx — GPX export.
  • format=geojson — GeoJSON export.
  • format=fit — FIT export.
GET /api/routes/455/export?format=gpx
GET /api/routes/455/export?format=geojson
GET /api/routes/455/export?format=fit

Activity export formats

  • format=gpx — generated GPX export.
  • format=fit — generated FIT export.
  • format=original — original uploaded track file (owner only).
GET /api/activities/1201/export?format=gpx
GET /api/activities/1201/export?format=fit
GET /api/activities/1201/export?format=original

For shared or public items, include the share key k when required: /api/routes/<id>/export?format=gpx&k=<share_key>. Invalid values return {"error":"invalid_format"}.

Response objects

JSON responses from OAuth and public API endpoints use the objects below. Arrays are shown with []. Optional fields may be omitted or returned as null depending on the data available.

OAuth token response

{
  "access_token": "<opaque token>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "<opaque token>"
}
  • access_token — credential sent as Authorization: Bearer <token>.
  • token_type — currently Bearer.
  • expires_in — access token lifetime in seconds.
  • refresh_token — token used to obtain new access tokens.

Profile object

{
  "id": 42,
  "email": "[email protected]",
  "username": "trailrunner"
}
  • id — internal user ID.
  • email — user email address.
  • username — public username.

Activity summary object

{
  "id": 1201,
  "name": "Sunday long run",
  "activity_type": "walking",
  "status": "ready",
  "activity_started_at": "2026-03-29T06:30:00+00:00",
  "created_on": "2026-03-29T07:02:12+00:00",
  "distance_m": 12984.3,
  "duration_s": 4210.0,
  "tags": ["long", "easy"]
}
  • id, name — activity ID and title.
  • activity_type — activity type label.
  • status — processing state such as ready.
  • activity_started_at, created_on — ISO 8601 timestamps when available.
  • distance_m, duration_s — distance in meters and duration in seconds.
  • tags — string labels attached to the activity.

GET /api/v1/activities returns:

{
  "activities": [ActivitySummary, ...]
}

Activity detail object

{
  "name": "Sunday long run",
  "route_line": {
    "type": "LineString",
    "coordinates": [[18.4241, -33.9249], [18.4250, -33.9258]]
  },
  "elevation": {
    "range_height": [[0, 14.2], [500, 24.1]]
  },
  "way_types": {
    "total_m": 1200,
    "segments": [{"start_m": 0, "end_m": 400, "key": "footway", "label": "Footway"}]
  }
}
  • route_line.coordinates — coordinate pairs in [lng, lat] order.
  • elevation.range_height[distance_m, elevation_m] points.
  • way_types.segments — per-surface or path segments by distance range.

Route summary object

{
  "id": 455,
  "name": "Table Mountain Loop",
  "costing_mode": "hiking",
  "created_on": "2026-03-27T10:12:03+00:00",
  "tags": ["weekend", "trail"],
  "start_place": {
    "city": "Cape Town",
    "region": "WC",
    "country": "ZA"
  }
}
  • costing_mode — route mode used for routing.
  • start_place — reverse-geocoded start context, when available.

GET /api/v1/routes returns:

{
  "routes": [RouteSummary, ...]
}

Route detail object

{
  "id": 455,
  "name": "Table Mountain Loop",
  "costing_mode": "hiking",
  "tags": ["weekend", "trail"],
  "description": "Steady climb with ocean views.",
  "start_place": {"city": "Cape Town", "region": "WC", "country": "ZA"},
  "waypoints": [[18.4241, -33.9249], [18.4320, -33.9320]],
  "route_line": {"type": "LineString", "coordinates": [[18.4241, -33.9249], [18.4320, -33.9320]]},
  "elevation": {"range_height": [[0, 10.1], [1500, 320.7]]},
  "way_types": {"total_m": 1500, "segments": [{"start_m": 0, "end_m": 500, "key": "path", "label": "Path"}]},
  "is_public": true
}
  • waypoints — user-defined route control points in [lng, lat].
  • is_public — whether the route is publicly visible.

Error object

{
  "error": "not_found",
  "detail": {...}
}
  • error — machine-readable key such as missing_authorization, not_found, or activity_not_ready.
  • detail — optional endpoint-specific context.

Scopes

The server currently advertises the following scopes. Your client registration may limit which of them it can request:

  • profile
  • activities_read
  • activities_write
  • routes_read
  • routes_write

Client types & PKCE

Confidential clients have a client secret and run on your server. Never embed the secret in mobile or browser apps.

Public clients have no secret. Use PKCE on the authorization and token steps so intercepted authorization codes cannot be exchanged without the original code_verifier.