Skip to main content
Documentation

Authentication

Create and manage WarpLink keys, tell an SDK key from an API key, and follow security best practices for authenticating requests.

All WarpLink API requests require authentication via Bearer token.

Key Types

TypeUse it forScopesWhere it goes
SDK keyiOS, Android, React Native, and Flutter appslinks:read and attribution:write, fixedWarpLink.configure()
API keyBackend scripts, CI, and AI agents via MCPYou chooseAuthorization header

Both use the wl_live_ prefix followed by 32 alphanumeric characters, so check the key type in the dashboard rather than reading the string. Only an SDK key can record install attribution. An API key placed in a mobile SDK resolves deep links normally but silently fails attribution.

An SDK key ships inside your app, so it can call only the three SDK endpoints: GET /v1/sdk/validate, GET /v1/links/resolve/{slug} and POST /v1/attribution/match. Every other endpoint and the MCP server refuse it with 403 SDK_KEY_ROUTE_FORBIDDEN: "SDK keys can only call the SDK endpoints. Use an API key for this request." To list links, manage apps or connect an AI client, create an API key.

Tie an SDK Key to One App

An SDK key can work for any app in your organization, or for exactly one. Choose the app when you create the key. A tied key acts only as that app, so a key shipped in one app cannot record installs for another. A request from a different app gets 403 KEY_APP_MISMATCH. Tied or not, an SDK key works only on the SDK endpoints, as described above.

You cannot change a key's app after creation. To move an integration to another app, create a new key and revoke the old one. Deleting an app revokes the SDK keys tied to it. API keys are never tied to an app. Keys created before this option existed keep working for any app.

Creating Keys

  1. Log in to the WarpLink dashboard
  2. Go to Keys
  3. Open the SDK keys tab for a mobile app, or the API keys tab for everything else, then click Create SDK key or Create API key
  4. Name the key. For an SDK key, choose the app it is for, or Any app. API keys also take a set of scopes and an optional expiry; SDK keys are pre-scoped and never expire
  5. Copy the key immediately. It's only shown once

Or create API keys via the API:

To create an SDK key tied to one app, send "type": "sdk" and the app's id as app_id. An app_id that is not an app of your organization is a validation error.

Using Keys

Include the key in the Authorization header:

Scopes

ScopeAccess
links:readRead link data
links:writeCreate, update, delete links
apps:readRead app configuration
apps:writeCreate, update, delete apps
analytics:readRead click analytics
attribution:writeRecord install attribution. SDK keys only, not requestable on an API key
keys:readRead API key metadata
keys:writeCreate and revoke API keys
domains:readRead custom domain configuration
domains:writeCreate, verify, and delete custom domains
org:readRead organization details
team:readRead team members and invitations
team:writeInvite, remove, and manage team members

Requesting links:write automatically includes links:read.

How It Works End to End

Every authenticated request carries a bearer token in the Authorization header. The server never stores that token in a form it could leak: on creation, it generates the raw key, computes a SHA-256 hash of it, and stores only the hash. Nothing in the database, in logs, or in a backup ever holds the plaintext key again after the response that returned it. When a request comes in, the server hashes the presented token the same way and looks up that hash. A match returns the key's organization, its scopes, who created it, and whether it's an SDK key or an API key. No match, an expired expires_at, or a malformed header all fail the same way: 401 UNAUTHORIZED.

Requesting links:write automatically includes links:read, so a key that can create and update links can always read them back too; you don't need to list both explicitly.

Two authorization layers apply on top of that lookup, and they're independent of each other:

  1. Scope check. The key must carry the scope the endpoint requires (links:write to create a link, keys:write to create another key, and so on). This is enforced purely from the row the key hash resolved to.
  2. Role check, dashboard-created keys only. A key created through the dashboard UI carries the id of the team member who created it (created_by). For those keys, the server also looks up that member's current role in the organization and checks it against the same role-permission matrix the dashboard UI itself uses (owner, admin, or member). If that member has since left the organization, the key is refused with 403 FORBIDDEN even though the key itself is still valid, so removing someone from a team immediately neutralizes keys they created.

Keys created before this second check existed, and any key created through the API without RBAC context, have created_by set to null. Those keys skip the role check entirely and are authorized on scopes alone. A null creator means no team member's departure or demotion applies to the key.

SDK Key vs. API Key, in Practice

The two key types are enforced, not just documented. An SDK key is minted with exactly links:read and attribution:write, nothing you request changes that, and it can call only the SDK endpoints whatever scopes it holds. An API key is minted with whatever scopes you choose from the requestable set, and attribution:write is deliberately not one of the choices, no matter which scopes you ask for. That's what makes the failure mode in the table above possible in the first place: swap an API key into a mobile app's configure() call, and it authenticates fine and resolves deep links fine, because both only need links:read. Attribution requests hit the same authorization check as everything else, see no attribution:write scope on the key, and are refused with 403 FORBIDDEN. Nothing crashes and nothing looks broken in the app. The only symptom is that installs never show up in the dashboard, which is why this is the first thing to check when attribution numbers stay at zero.

MCP: OAuth or API Key

The MCP server at /mcp is the one surface that accepts two different kinds of credentials on the same endpoint, and it tells them apart from the token itself rather than from a separate parameter. A bearer token that starts with wl_ is treated as an API key and goes through the same lookup described above. Anything else is treated as an OAuth access token and verified against WarpLink's own OAuth authorization server.

Both paths converge on the same authorization context (organization, scopes, role) before a tool call runs, so a tool doesn't need to know which path authenticated the caller. What differs is the experience of connecting:

  • OAuth is the one-time browser sign-in flow most MCP clients use by default: you approve the connection once in a browser, and the client stores a token it refreshes on its own. This is the natural fit for an interactive AI assistant a person is using directly.
  • API key is a bearer token you paste into the client's configuration once, with no browser step. This is the natural fit for non-interactive use: a CI pipeline, a scheduled job, or any environment where nobody is present to click through a browser consent screen.

A multi-organization OAuth connection that hasn't chosen a default organization can still call every tool; each call resolves its organization from the tool's own org argument instead of a connection-wide default. An API key has no such ambiguity, since a key is always scoped to exactly one organization at creation time.

Worked Example

Creating a scoped API key and then using it look like this:

The raw_key field only appears in this create response. Reading the key back later, via GET /v1/keys, returns the same row without it:

Only the prefix survives past the create response. If a key is lost, the only remedy is revoking it and creating a new one; there's no way to recover or re-display the original value.

Troubleshooting

Every request returns 401 UNAUTHORIZED. Check three things in order: the header is exactly Authorization: Bearer wl_live_... (not Basic, not a bare token with no Bearer prefix), the key hasn't been revoked in the dashboard, and if the key was created with an expiry, that expires_at hasn't passed. All three produce the identical error, by design, so a caller can't use the response to distinguish "wrong key" from "expired key" from "revoked key."

Deep links work but no installs show up. This is almost always an API key doing an SDK key's job. Confirm the key passed to WarpLink.configure() was created as an SDK key, not an API key with scopes you picked yourself. Only an SDK key carries attribution:write.

A key that used to work now returns 403 FORBIDDEN. If the key was created through the dashboard, check whether its creator is still a member of the organization, and whether their role still has the permission the endpoint requires. A demotion from admin to member, or a removal from the org, takes effect on that key immediately, even though the key itself was never touched.

I revoked or lost a key and need to keep the integration running. Create a new key with the same scopes and swap it in. There is no way to view or restore a previous key's raw value, since only its hash is ever stored.

FAQ

How long is wl_live_ followed by? 32 alphanumeric characters, for 33 total characters after the wl_ prefix including the literal live_. Treat the whole string as opaque; don't parse or truncate it.

Do API keys expire by default? No. expires_at is optional and unset unless you choose one when creating the key. SDK keys never expire, since an app you've shipped can't prompt anyone to rotate a credential.

Can I see a key's value again after creation? No. The raw key is returned exactly once, in the create response. Only its prefix (wl_live_) is shown afterward, which is enough to tell keys apart in a list but not enough to authenticate with.

What's the difference between revoking and deleting a key? There isn't one functionally: revoking an API key is permanent. A revoked key's hash no longer matches on lookup, so every request using it gets 401 UNAUTHORIZED, the same as a key that never existed.

Why did my API key stop working after I left the team that created it? If the key was created through the dashboard by a specific member, and RBAC applies to it (its created_by is set), the server re-checks that member's current role on every request. Losing membership or being demoted below the permission an endpoint requires fails that check, even though nothing about the key changed.

Dashboard Sign-In Security

Dashboard sign-in supports authenticator app (TOTP) two-factor authentication, with backup codes. Organization owners can require two-factor authentication for every member, and can set a session lifetime and an idle timeout for the organization. An audit log in the dashboard records account and security events for the organization. These settings protect the dashboard; API keys and SDK keys are governed by the key rules above.

Security Best Practices

  • Never commit API keys to source control
  • Use environment variables to store keys
  • Create separate keys for different environments (production, staging, CI)
  • Set expiration dates on keys that don't need permanent access
  • Use the minimum scopes required for each key
  • Rotate keys periodically and after team member departures