Skip to main content
Documentation
API Reference

Keys

Keys endpoints to create, list, get, and revoke the SDK keys and API keys that authenticate requests to WarpLink, including tying an SDK key to one app.

Every request to the WarpLink API authenticates with a key issued through this endpoint set. A key belongs to exactly one organization, is created once, and shows its raw value exactly once: in the response to the create call. From then on, the server stores only a SHA-256 hash of it, so nothing in the database, in logs, or in a backup ever holds the plaintext again. Everything else in this API, and every SDK running on a device, is authenticated by one of these keys.

There are two kinds of key, distinguished by the type field on creation, and they answer different needs. An api key is what you choose the scopes for yourself, meant for backend scripts, CI, MCP clients, and anything a human or a service controls directly. An sdk key is pre-scoped with exactly links:read and attribution:write, meant to be embedded in a mobile app's WarpLink.configure() call, and it never expires, because an app you've already shipped can't prompt anyone to rotate a credential. Both key types share the same wl_live_ prefix followed by 32 alphanumeric characters, so they're indistinguishable by sight; the dashboard's SDK keys and API keys tabs are where you tell them apart. See Authentication for the full comparison of the two key types.

Authentication and Permissions

Managing keys, meaning creating, listing, reading, or deleting any of them, is gated the same way regardless of which operation you're calling:

OperationScopeRole required (dashboard-created keys)
Create a keykeys:writeowner or admin (keys:manage)
List keyskeys:readowner or admin (keys:manage)
Get a keykeys:readowner or admin (keys:manage)
Delete a keykeys:writeowner or admin (keys:manage)

There is no keys:manage scope on the key itself; that name belongs to the role-permission matrix, checked in addition to the scope for any key that was created through the dashboard UI and therefore carries a created_by. A member role never holds keys:manage, so even a key with keys:read scope that a member created cannot list or read other keys. Key management in WarpLink is an admin-and-above surface, the same as team invitations and app configuration. A key created without dashboard context (created_by is null) skips the role check and is authorized on scope alone, which keeps keys created directly through the API working exactly as they always have.

Validation and Behavior

  • type is api or sdk. Omitting it defaults to api, for backward compatibility with clients that predate the field.
  • An api key requires name (1 to 100 characters) and scopes (at least one, from the requestable set). Requesting links:write without links:read adds links:read automatically, so a key that can write links can always read them back.
  • attribution:write is not in the requestable scope list. It is reserved for sdk keys and auto-granted to them; asking for it on an api key is rejected the same as any other invalid scope value.
  • An sdk key requires only name. Its scopes (links:read, attribution:write) and its lack of an expiry are fixed by the server, not by the request body.
  • expires_at, valid only on api keys, must be an ISO 8601 date strictly in the future. sdk keys cannot be given an expiry at all.
  • app_id is valid only on sdk keys. It ties the key to one app of your organization: the key then works only with the WarpLink SDK and only as that app. Omit it, or send null, for an SDK key that works for any app in the organization. On an api key any non-null app_id is rejected with 400 VALIDATION_ERROR, and details.fields contains { "field": "app_id", "message": "API keys cannot be tied to an app" }.
  • An app_id that is not an app of your organization is rejected with 400 VALIDATION_ERROR, and details.fields contains { "field": "app_id", "message": "App not found" }. The message is the same for an unknown id and for another organization's app, so it cannot be used to look up app ids.
  • A key's app cannot be changed after creation. To use a key for another app, create a new SDK key and delete the old one. Deleting an app deletes the SDK keys tied to it.
  • An SDK key, tied to an app or not, is accepted only on the SDK endpoints (/v1/sdk/validate, /v1/links/resolve/{slug} and /v1/attribution/match). Every other endpoint, including this one, and the MCP server answer it with 403 SDK_KEY_ROUTE_FORBIDDEN. On attribution, a request from a different app gets 403 KEY_APP_MISMATCH.
  • The raw key format is wl_<environment>_ followed by 32 base62 characters. Every key created through this API is a live key; there is no way to request a wl_test_ key here.
  • There is no plan limit on the number of keys an organization can hold. Every plan, including Free, can create as many keys as it needs.

Create a Key

POST/keys

Create a key

Request Body

FieldTypeDescription
type"api" | "sdk"`sdk` creates a mobile SDK key with fixed scopes that never expires. `api` is the default.
name*string
app_idstring | nullOnly for `sdk` keys. Ties the key to one app of your organization, so it works only with the SDK and only for that app. Omit or send null for a key that works for any app. An `api` key cannot be tied to an app. An id that is not an app of your organization is a validation error.
scopes"links:read" | "links:write" | "apps:read" | "apps:write" | "analytics:read" | "analytics:write" | "keys:read" | "keys:write" | "domains:read" | "domains:write" | "org:read" | "team:read" | "team:write"[]Required for `api` keys. Ignored for `sdk` keys, whose scopes are fixed.
expires_atstringMust be in the future.

Responses

201Key created. The `raw_key` field is only returned once.
400Validation error
FieldTypeDescription
error*object
401Unauthorized
FieldTypeDescription
error*object
403Forbidden
FieldTypeDescription
error*object

The raw_key field in this response is the only place the plaintext key ever appears. Every other response in this API, including the immediate GET of the same key, returns key_prefix (wl_live_) instead, which is enough to tell keys apart in a list but not enough to authenticate with. If you lose the raw value, there's no recovery path other than deleting the key and creating a new one.

List Keys

GET/keys

List keys

Responses

200List of API keys
FieldTypeDescription
dataobject[]
401Unauthorized
FieldTypeDescription
error*object
403Forbidden
FieldTypeDescription
error*object

Returns every key in the organization, both api and sdk types, ordered as stored. Every item carries app_id: the app an sdk key is tied to, or null for api keys and for sdk keys that work for any app. There is no pagination on this endpoint and no filter by type; an organization managing many keys filters client-side on the type field in the response.

Get a Key

GET/keys/{id}

Get a key

Parameters

NameTypeInDescription
id*stringpath

Responses

200API key details
FieldTypeDescription
idstring
type"api" | "sdk"
key_prefixstringe.g. wl_live_
namestring
app_idstring | nullThe app an `sdk` key is tied to. Null for `api` keys and for `sdk` keys that work for any app.
scopesstring[]
last_used_atstring | null
expires_atstring | null
created_atstring
updated_atstring
401Unauthorized
FieldTypeDescription
error*object
403Forbidden
FieldTypeDescription
error*object
404Not found
FieldTypeDescription
error*object

Returns one key's metadata by its id, the same shape as an item in the list response. This is a metadata read, not an authentication check; it never returns or confirms a raw key value, and a request against a key belonging to another organization is refused with 404 NOT_FOUND rather than 403, so a caller can't use this endpoint to discover which key IDs exist elsewhere.

Delete a Key

DELETE/keys/{id}

Delete a key

Parameters

NameTypeInDescription
id*stringpath

Responses

204Deleted
401Unauthorized
FieldTypeDescription
error*object
403Forbidden
FieldTypeDescription
error*object
404Not found
FieldTypeDescription
error*object

Deleting is permanent and immediate. The key's hash no longer matches on lookup the moment the row is gone, so any request still using that key, including one already in flight, fails with 401 UNAUTHORIZED from that point on, indistinguishable from a key that never existed. There is no separate "revoke" state to reactivate from; deleting an sdk key embedded in a shipped app breaks attribution and deep link resolution for every install still using it, so rotate before you delete when a mobile app depends on the key.

Worked Example

Create a scoped API key for a CI pipeline, confirm it by listing keys, then delete it once the pipeline is retired:

Creating an SDK key for one mobile app looks the same, minus the scopes and expiry, plus the app it is tied to:

The delete call returns 204 No Content on success, with no body to parse.

Common Errors

StatusCodeCause
400VALIDATION_ERRORMissing name, empty scopes, an invalid scope value, an expires_at that isn't in the future, an app_id on an api key, or an app_id that is not an app of your organization
400INVALID_IDThe id path parameter isn't a valid UUID
401UNAUTHORIZEDMissing, invalid, or expired API key on the request itself
403FORBIDDENThe calling key lacks keys:read or keys:write, or its creator's role lacks keys:manage
403SDK_KEY_ROUTE_FORBIDDENThe calling key is an SDK key. SDK keys can only call the SDK endpoints. Use an API key for this request
404NOT_FOUNDNo key with that ID exists in this organization

FAQ

Can I tie an SDK key to one app? Yes. Send app_id when you create an sdk key. The key then acts only as that app. You cannot change it later, and api keys cannot be tied. Tied or not, an SDK key can call only the SDK endpoints.

Can I see a key's raw value again after creation? No. raw_key appears exactly once, in the create response. Every later read, including GET /v1/keys/{id}, returns key_prefix only.

What's the difference between an api key and an sdk key? An api key carries whatever scopes you request and can carry an expiry; an sdk key is fixed to links:read and attribution:write, never expires, and is the only credential type that can record install attribution. Putting an api key into a mobile app's configure() call resolves deep links fine but silently fails attribution, since it can't hold attribution:write.

Can I request attribution:write on an api key? No. It's excluded from the requestable scope list entirely and is only ever granted automatically, to sdk keys.

Is there a limit on how many keys I can create? No. Key count isn't one of the plan-gated resources; every plan, including Free, can create as many keys as needed.

Can a member role create or manage API keys? No. Key management requires the keys:manage permission, which only owner and admin roles hold, even for a read-only GET request with a properly scoped key.