Using the TIPS API

The TIPS API exposes your surveys, responses, Traveler Profiles, mailing lists, and usage data as JSON over plain HTTPS — ready for your own dashboards, BI pipelines, AI tooling, or anything else that speaks REST. v1 is read-only by design.

TL;DR. Base URL https://travelerips.com/api/v1/. Auth via Authorization: Bearer tips_… per-Client keys you mint in the Client Portal. Read-only (GET only). Cursor pagination on lists. Per-key rate limits surfaced via X-RateLimit-* headers. Optional IP allowlist per key. JSON in, JSON out (plus GeoJSON for route geometry).

Generating an API key

  1. Sign in to your Client Portal.
  2. Click your name in the top-right nav and choose API Keys.
  3. Click + Create API key, give it a label (e.g. "n8n integration", "internal dashboard"), and optionally paste in an IP allowlist (one IP or CIDR per line).
  4. Copy the displayed raw key immediately to a secret manager. The raw value is shown exactly once; TIPS only ever stores its SHA-256 hash. If you lose it, revoke the key and create a new one.

Keys belong to the Client account, not to an individual Client User. Any active Client User can mint, edit, or revoke keys.

Authentication

Every API request must include an HTTP header:

Authorization: Bearer tips_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

Missing, malformed, or invalid bearer tokens return 401 invalid_credentials. Revoked keys return the same generic 401 (we never confirm or deny key existence in error messages). Failed auth responses are padded to a minimum response time to defeat timing-attack probes.

A simple curl test

curl -H "Authorization: Bearer $TIPS_API_KEY" \
     https://travelerips.com/api/v1/surveys

Endpoint reference (v1)

Base URL: https://travelerips.com/api/v1/

Method Path Returns
GET/Service info ping (no auth required).
GET/surveysList of surveys, cursor-paginated.
GET/surveys/{id}Full survey definition (same shape as Survey Export).
GET/surveys/{id}/routes.geojsonGeoJSON FeatureCollection of every Traveler-drawn route on the survey.
GET/responsesFlat list of completed responses. Filterable by surveyId, since, until, verified.
GET/responses/{id}One response with all answer data + route geometry, grouped by field.
GET/travelersTraveler Profiles, cursor-paginated. Filter by status=subscribed|opted_out.
GET/travelers/{id}One Traveler + list memberships + response history.
GET/mailing-listsMailing Lists with profile counts.
GET/mailing-lists/{id}/membersMembers of one list, cursor-paginated.
GET/usageCurrent-period usage vs. plan caps (mirrors /client/usage).

Response envelopes

List endpoints

{
  "data": [
    { "id": 4, "publicId": "5b9...", "title": "Spring corridor study", ... },
    { "id": 7, "publicId": "92a...", "title": "Bus stop relocation", ...  }
  ],
  "pagination": {
    "limit": 100,
    "nextCursor": "Nw",
    "hasMore": true
  }
}

Single-resource endpoints

{
  "data": { "id": 4, "publicId": "5b9...", ... }
}

Error envelope

{
  "error": "rate_limited",
  "message": "Exceeded 30 requests/minute on this key."
}

Cursor pagination

List endpoints accept ?limit=<n> (default 100, max 500) and ?cursor=<opaque>. The opaque cursor is a base64-encoded id of the last seen row — never construct your own, always pass what the previous response returned in pagination.nextCursor.

# First page
curl -H "Authorization: Bearer $TIPS_API_KEY" \
  "https://travelerips.com/api/v1/responses?surveyId=4&limit=200"

# Subsequent pages — pass the previous nextCursor
curl -H "Authorization: Bearer $TIPS_API_KEY" \
  "https://travelerips.com/api/v1/responses?surveyId=4&limit=200&cursor=MTk5"

When pagination.hasMore is false, the walk is complete. The cursor is stable under writes — new rows arriving mid-walk don't shift your position.

Rate limits

Every authenticated response carries:

X-RateLimit-Limit-Minute: 30
X-RateLimit-Remaining-Minute: 27
X-RateLimit-Limit-Day: 5000
X-RateLimit-Remaining-Day: 4912

When you exceed either window, the response is 429 rate_limited with a Retry-After header (60 for the per-minute cap, 3600 for the per-day cap). Polite clients should back off on 429 rather than retrying immediately; standard libraries (requests/httpx in Python, axios/got in Node) all support automatic retry-after handling.

Examples

Python (requests)

import os, requests

BASE = "https://travelerips.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['TIPS_API_KEY']}"}

# Walk every response for survey #4
url = f"{BASE}/responses?surveyId=4&limit=500"
while url:
    r = requests.get(url, headers=HEADERS, timeout=30)
    r.raise_for_status()
    body = r.json()
    for resp in body["data"]:
        print(resp["id"], resp["dateCompleted"], resp["verified"])
    nxt = body["pagination"]["nextCursor"]
    url = f"{BASE}/responses?surveyId=4&limit=500&cursor={nxt}" if body["pagination"]["hasMore"] else None

Node (fetch)

const BASE = "https://travelerips.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.TIPS_API_KEY}` };

async function listSurveys() {
  let url = `${BASE}/surveys?limit=200`;
  while (url) {
    const r = await fetch(url, { headers });
    if (!r.ok) throw new Error(`API ${r.status}: ${await r.text()}`);
    const body = await r.json();
    for (const s of body.data) console.log(s.id, s.title);
    url = body.pagination.hasMore
      ? `${BASE}/surveys?limit=200&cursor=${body.pagination.nextCursor}`
      : null;
  }
}
listSurveys().catch(console.error);

curl (one-shot survey export)

# Save the full definition of survey #4 as JSON
curl -H "Authorization: Bearer $TIPS_API_KEY" \
     https://travelerips.com/api/v1/surveys/4 \
     | jq '.data' > survey-4.json

# Save every Traveler-drawn route on survey #4 as GeoJSON
curl -H "Authorization: Bearer $TIPS_API_KEY" \
     https://travelerips.com/api/v1/surveys/4/routes.geojson \
     > survey-4-routes.geojson

Security model

  • Hash-only storage. TIPS stores SHA-256 of the raw key plus the first 8 chars (for UI display). The raw value leaves the server exactly once at creation.
  • Per-Client scope. Every endpoint scopes by the owning Client of the bearer token. You cannot reach another Client's data even if you guess their internal ids.
  • IP allowlist. Optional per-key CIDR list. Production deployments should always set one.
  • Constant-time auth checks. Key lookup uses SHA-256 + a unique index; failed-auth response time is padded to defeat timing oracles.
  • Audit log. Every request (method, path, status, IP, duration) is written to API Request Log and that is viewable via the Api-Keys page. These are pruned after 90 days.
  • Revoke instantly. Revocation is effective on the next request; there is no stale-cache window.

Pairing the API with AI tools

A common pattern is to point an LLM agent (Claude, ChatGPT, your own model) at the TIPS API with a per-Client key. The agent can call /surveys to discover what's on the account, /responses to pull the response set, and /usage to ground recommendations in current quota headroom. Because the API is read-only and per-Client-scoped, you can issue a key for the agent without worrying it will accidentally mutate state or reach other Clients' data.

Common questions about the API

How do I get an API key?
Open the Client Portal, click your name in the top-right nav, choose API Keys, and click "+ Create API key." The raw key is displayed exactly once — copy it to a secret manager immediately. We only store the hash; if you lose the key, revoke it and create a new one.
What plans include API access?
API access is gated by the apiAccessEnabled flag on your service tier. Trial plans do not include API access; Professional and Enterprise tiers do. Check the /client/api-keys page — if API access is disabled, you'll see an upgrade prompt with a link to /client/billing.
What are the rate limits?
Per-key limits are set on your service tier (typically 30 requests per minute and 5,000 per day on Professional, higher on Enterprise). Every successful response includes X-RateLimit-Limit-Minute, X-RateLimit-Remaining-Minute, X-RateLimit-Limit-Day, and X-RateLimit-Remaining-Day headers. Exceeding either returns HTTP 429 with a Retry-After header.
How does IP allowlist enforcement work?
When you create or edit an API key, you can list one IP or CIDR range per line. Requests from any IP outside the allowlist return HTTP 403 with error code "ip_not_allowed". Leave the allowlist empty to permit any IP — convenient for development but less secure for production keys.
Is the API read-only?
Yes. v1 of the TIPS API is GET-only. You can list and retrieve every resource your account owns; you cannot create, update, or delete anything. Write endpoints may arrive in a future major version with explicit per-resource scopes.
What's the response format?
JSON with UTF-8 encoding. List endpoints wrap results in {"data": [...], "pagination": {...}}; single-resource endpoints wrap in {"data": {...}}. The GeoJSON endpoint (/surveys/{id}/routes.geojson) returns a standards-compliant GeoJSON FeatureCollection.
How does cursor pagination work?
Pass ?limit=<n> (default 100, max 500) and ?cursor=<opaque> to walk results in stable batches. Every paginated response returns pagination.nextCursor; when hasMore is true, pass nextCursor as ?cursor= on the next request. When hasMore is false, you're done.
What error format does the API use?
Errors return JSON with {"error": "<code>", "message": "<human-readable>"} and the appropriate HTTP status (400, 401, 403, 404, 405, 429, 503). Error codes are stable and machine-parseable: invalid_credentials, key_revoked, api_not_enabled, ip_not_allowed, rate_limited, not_found, method_not_allowed, service_unavailable.

Ready to wire something up?

Open the Client Portal, mint a key, and start with curl https://travelerips.com/api/v1/ to confirm connectivity — then walk over to the endpoint reference above.

View plans Contact us