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.
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
- Sign in to your Client Portal.
- Click your name in the top-right nav and choose API Keys.
- 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).
- 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 | /surveys | List of surveys, cursor-paginated. |
| GET | /surveys/{id} | Full survey definition (same shape as Survey Export). |
| GET | /surveys/{id}/routes.geojson | GeoJSON FeatureCollection of every Traveler-drawn route on the survey. |
| GET | /responses | Flat 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 | /travelers | Traveler Profiles, cursor-paginated. Filter by status=subscribed|opted_out. |
| GET | /travelers/{id} | One Traveler + list memberships + response history. |
| GET | /mailing-lists | Mailing Lists with profile counts. |
| GET | /mailing-lists/{id}/members | Members of one list, cursor-paginated. |
| GET | /usage | Current-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 Logand that is viewable via theApi-Keyspage. 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.
Putting this to work
TIPS is the survey platform behind everything described above. These two pages cover the part most relevant to what you just read.