IRLwith.meDevelopers
Documentation / version 1.0

Build alongside
the adventure.

A REST API for the IRLwith.me resources you own. Read adventures, route stops, missions, challenges, poll results and privacy-filtered telemetry. Create private plans and update adventure descriptions.

https://dev.irlwith.me/api/v1
This API covers IRLwith.me only. KICK chat, channels, subscriptions, moderation, rewards, OAuth tokens and event subscriptions belong to KICK’s API. IRLwith.me keys cannot call KICK.

This release uses personal, account-owned API keys for server integrations. It does not offer delegated third-party OAuth, inbound telemetry writes, streaming transport, webhooks, or every internal dashboard operation.

01 / Your first request

  1. Sign in at the Developer Console with your IRLwith.me account.

  2. Create a key named for your integration. Select account:read and adventures:read, then choose an expiry.

  3. Copy the key immediately. It is displayed only once. Store it as an environment variable on your server.

export IRL_API_KEY='irl_live_REPLACE_WITH_YOUR_KEY'
curl --fail-with-body https://dev.irlwith.me/api/v1/me \
  -H "Authorization: Bearer $IRL_API_KEY"
{ "data": { "id": 123, "roles": ["viewer", "creator"] } }

List adventures with GET /adventures, then use an ID from that response for nested endpoints. A viewer account with no adventures receives an empty list. Enable Creator in the main app before creating plans.

02 / Authentication & key lifecycle

Every v1 request requires Authorization: Bearer <key>. Session cookies and query-string tokens are not API authentication. Use HTTPS. Keys contain 256 random bits and begin with irl_live_; the server stores a SHA-256 hash, never the recoverable secret.

You can hold ten active keys. Expiry is 1–365 days (90 by default). Names and scope selections are fixed after creation. To rotate, create a replacement, update your integration, verify it, then revoke the old key. Revocation takes effect on its next request; requests already running may complete. Expired and revoked secrets cannot be restored.

The console displays each prefix, scope set, expiry, last use and lifetime request count, plus daily authenticated request/error totals for the last 30 UTC days. Invalid keys are not attributed to an account. Metadata is bounded to the latest 100 keys; older inactive key history may be removed. Managed creator sessions cannot manage developer keys. Suspended accounts cannot use keys. Account deletion cascades to keys and usage.

03 / Scopes & privacy

Scopes are independent: write does not imply read. A key can only access its owner’s resources, including private adventures. An unknown ID and another account’s ID both return 404. Administrator roles do not grant cross-account API access.

The adventures:read scope exposes planned waypoint coordinates even when live telemetry is hidden. These are your private route plans. Grant that scope only to trusted server integrations.

telemetry:read always uses the existing adventure privacy mode: exact, approximate, delayed or hidden. Hidden or unavailable telemetry returns null. Emergency privacy suppresses coordinates. Approximate mode rounds location; delayed mode uses eligible history. No RTIRL pull keys, raw telemetry history, overlay tokens, private notes, KICK identifiers or provider credentials are returned.

04 / Endpoint reference

JSON responses wrap the resource in data. List responses add pagination. Names are case-sensitive. IDs are positive integers. Send JSON objects with Content-Type: application/json on writes; unknown write fields are rejected.

Adventure write contract

POST /adventures requires name (2–120 characters). Optional description (up to 2,000 characters, defaults empty) and category (1–80 characters, defaults “Road Trip”). It creates a private adventure in planning status with approximate location privacy and initializes its dashboard/overlay state. It returns 201 with a Location header.

PATCH /adventures/{id} accepts those same three fields, all optional but at least one required. Changes update the existing adventure. Status, visibility, privacy, owner and KICK-related fields cannot be changed here. No API write sends KICK messages or makes KICK API requests.

curl --fail-with-body https://dev.irlwith.me/api/v1/adventures \
  -X POST -H "Authorization: Bearer $IRL_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Coastal walk","description":"A new route plan"}'

Response fields

  • Adventure: id, name, description, category, visibility, status, privacyMode, createdAt, updatedAt. SQL timestamps are UTC YYYY-MM-DD HH:mm:ss.
  • Statistics: miles_today, miles_total, stops_completed, missions_completed, challenges_completed, bounties_completed, submissions_accepted, xp, rank. Returns null if no statistics row exists.
  • Waypoint: id, title, description, latitude, longitude, type, status, position. Listed by ID; sort by position to reconstruct route order.
  • Objectives: data.missions and data.challenges. Missions include id, title, description, difficulty, points, status. Challenges include id, title, description, rewardPoints, category, status.
  • Poll: id, title, type, status, createdAt, closesAt, options. Each option has id, label, position, votes. Votes are aggregate counts; voter identity is excluded. Soft-deleted polls are excluded. Status reflects persisted lifecycle state.
  • Telemetry: existing privacy-filtered sensor/location fields or null. Fields can be absent or null; do not assume every device reports speed, heading or altitude. Check recorded_at before treating a reading as current. Schema is extensible.

Download the OpenAPI 3.0 specification for request/response schemas and API-client imports.

05 / Pagination

Lists accept limit (1–100, default 25) and after (nonnegative resource ID, default 0). Results are ordered by ascending ID. Pass pagination.nextAfter as after until it is null. Unknown list query parameters return 400.

GET /adventures?limit=25&after=123

{ "data": [...], "pagination": { "limit": 25, "nextAfter": 148 } }

Objectives have two collections and return missionsNextAfter and challengesNextAfter. The after cursor applies independently to both. Paginate each collection separately using its cursor and consume only that collection on subsequent requests. Collections are not snapshot-isolated across requests.

06 / Errors, limits & retries

{ "error": {
  "code": "insufficient_scope",
  "message": "Required scope: adventures:read",
  "requestId": "..."
} }
HTTPMeaningAction
400Invalid input / malformed JSONCorrect fields, query or ID.
401Invalid, absent, revoked or expired keyCheck Bearer header; rotate if needed.
403Scope, role or account restrictionSelect the needed scope; check account status.
404Unknown or unowned resource / routeUse IDs from your own list.
413 / 415Body too large / incorrect content typeKeep JSON below 1 MB; use application/json.
429Request limit reachedHonor Retry-After and add jitter.
500Internal failureUse bounded backoff for reads; retain request ID.

Authenticated API traffic is limited to 120 requests per minute per account across all keys. Responses include RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset (seconds until reset). A further shared app limit of 300 requests/minute per IP, plus edge protections, can apply and may return a different error format. Console management is limited to 30/minute per IP.

GET requests are safe to retry. Creation is not idempotent: do not automatically retry POST after an ambiguous network failure; check your adventure list first. PATCH sets descriptive values and can be retried, but may overwrite concurrent edits. There are no idempotency keys or optimistic concurrency tokens in v1. Keep polling to a reasonable interval, such as 15–30 seconds.

07 / Code examples

Node.js · server-side fetch

const response = await fetch(
  'https://dev.irlwith.me/api/v1/adventures?limit=25',
  { headers: { Authorization: `Bearer ${process.env.IRL_API_KEY}` } }
);
const body = await response.json();
if (!response.ok) throw new Error(JSON.stringify(body.error));
console.log(body.data);

Python · standard library

import json, os, urllib.request
request = urllib.request.Request(
    'https://dev.irlwith.me/api/v1/adventures?limit=25',
    headers={'Authorization': 'Bearer ' + os.environ['IRL_API_KEY']}
)
with urllib.request.urlopen(request, timeout=15) as response:
    adventures = json.load(response)
print(adventures['data'])

Read privacy-filtered location

curl --fail-with-body \
  https://dev.irlwith.me/api/v1/adventures/123/telemetry \
  -H "Authorization: Bearer $IRL_API_KEY"

Requires telemetry:read and an adventure you own. A null result is valid and must not be treated as an error. Never substitute cached exact coordinates after a hidden/emergency response.

08 / Integration security

  • Keep keys in server-side environment variables or a secret manager. Never put them in public JavaScript, mobile app bundles, OBS URLs, query strings or source control.
  • Use separate least-privilege keys for each integration and rotate regularly.
  • Do not request or share another person’s personal key. Delegated OAuth is not available in this release.
  • Clear cached private routes and telemetry when access is revoked, and honor privacy changes immediately.
  • The request explorer holds your entered key only in page memory, uses credentials:omit for API calls, and does not write it to local/session storage. Close the page when finished.
  • Usage totals contain counts and dates, not request bodies or raw keys.

09 / Versioning & support

Initial release: October 24, 2026 · v1.0.0. The URL contains the major version. Clients should ignore new response fields, handle nullable telemetry and avoid assuming fixed role/status lists. Breaking contract changes require a new major API version. No availability SLA is offered.

For help, use Support on IRLwith.me. Include the endpoint, HTTP status and X-Request-Id, but never your key, password or private coordinates.

Open Developer Console →
IRLwith.me Developer Platform · Download OpenAPI