Troubleshooting
Common symptoms, their causes, and how to fix them
This page collects the issues integrators hit most often, organized by symptom. Each entry names the likely cause and the concrete fix. For the full list of error codes, see Error Handling.
Authentication & Access
Every request returns 401 unauthorized
The key is missing or not being sent the way the API expects. Send it as a bearer token: Authorization: Bearer tl_live_xxx. A common mistake is sending the key as a raw header value without the Bearer prefix, or pasting a key that was truncated on copy. Confirm the key still exists and is enabled in Settings → API Keys.
A request returns 403 forbidden even though the key is valid
The key is authenticated but lacks the scope the endpoint requires. The message names the missing scope. For example, creating a geofence needs manage:geofences, not just read:geofences. Scopes are fixed at key creation—mint a new key with the scope you need rather than trying to edit an existing one. See Authentication.
403 api_disabled on the very first call
The API is not enabled for the workspace yet. Creating any API key from Settings → API Keys enables it automatically—create one and retry.
401 vs 403 vs 404 — which is which?
They are distinct on purpose. 401 means we could not authenticate the key at all. 403 means the key is valid but not allowed to do this (wrong scope, or API not enabled). 404 means the resource does not exist or is not visible to this key—which is also what a tag outside the key's allow-list looks like (see below).
Tags & Scoped Keys
A tag I can see in the dashboard returns 404 from the API
The key is probably restricted to a tag allow-list that does not include this tag. A scoped key only ever sees the tags it was granted; any other tag id resolves to not_found, by design, so a restricted key cannot probe for tags it shouldn't know about. Either use a key whose scope covers the tag, or add the tag to the key's allow-list when you create a replacement key.
GET /v1/tags returns fewer tags than I expect
If the key is scoped to an allow-list, the list endpoint only returns the tags in that list (and skips any that no longer exist). This is expected. A key scoped to all tags returns the full workspace set, page by page.
Pagination & Cursors
A page came back with fewer items than my limit, but there's a nextCursor
This is correct behavior, not a bug. Pages can be under-full when the window straddles gaps (for example, a scoped tag that was deleted). The rule is simple: keep paging until nextCursor is null. Never stop early just because a page looks short.
An empty page came back, but nextCursor is still set
Same rule applies. An empty page does not mean you have reached the end—only a null nextCursor means that. Pass the returned cursor to the next request and continue.
My pagination restarted from the beginning unexpectedly
Cursors are opaque. If you hand-build, truncate, or otherwise corrupt a cursor, it is treated as absent and the list starts over from the first page rather than erroring. Always pass back the exact nextCursor string you received, unmodified, and do not try to parse or construct cursors yourself.
Webhooks
POST /v1/webhooks rejects my URL with invalid_url
Delivery URLs must be public HTTPS endpoints. We reject anything that is not https://, that embeds credentials (https://user:pass@host), or that points at a private, loopback, or link-local address. Use a publicly reachable HTTPS URL. For local development, put a tunnel in front of your server and register the tunnel's public URL.
My webhook endpoint never receives events
Work through these in order:
- Confirm the subscription is
active. - Check the
eventsfilter. If you subscribed to specific event types, only those are delivered; omiteventsto receive all of them. - Make sure your endpoint answers with a
2xxquickly. Non-2xx responses are treated as failures and retried with backoff. - Inspect the delivery history endpoint to see attempts, status codes, and retry state.
My signature check always fails
Compute the HMAC-SHA256 over the exact raw request body bytes, using your subscription's signing secret, and compare against the hex digest in X-TagLogger-Signature (formatted sha256=<hex>). The two most common mistakes are hashing a re-serialized JSON object (which changes the bytes) instead of the raw body, and comparing against the whole header value without stripping the sha256= prefix. Use a constant-time comparison. See Webhook Delivery.
I received the same event more than once
Delivery is at-least-once, so retries can occasionally deliver a duplicate. Make your handler idempotent: dedupe on the event id and treat a repeat as a no-op.
Rate Limits & Share Links
I'm getting 429 rate_limited
You exceeded the per-key limit. Honor the Retry-After header and back off. If you hit it routinely, poll the fleet delta endpoint instead of looping per-tag, and widen your interval. The polling guide shows patterns that stay well under the limit.
A share link stopped working
Share links can carry an expiry. Once a link is past its expiration it no longer resolves. Create a fresh link with manage:share-links when you need continued access.
Still Stuck?
Capture the request method and path, the response status, and the error.code and message from the body, then reach out through your normal support channel. Never share an API key or signing secret in a support message—reference the key by its visible prefix instead.