Create an API key
const url = 'https://app.tunnelhq.com/api/v1/keys';const options = { method: 'POST', headers: {'X-API-Key': '<X-API-Key>', 'Content-Type': 'application/json'}, body: '{"name":"Staging CI","expires":"2026-12-31T23:59:59Z"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://app.tunnelhq.com/api/v1/keys \ --header 'Content-Type: application/json' \ --header 'X-API-Key: <X-API-Key>' \ --data '{ "name": "Staging CI", "expires": "2026-12-31T23:59:59Z" }'Creates a key in the same workspace as the key making the request, and returns its secret
once. The request must use a workspace key, from a workspace whose plan includes API access:
the account-wide key from thq login is refused. The new key starts with uk.
Need an API key? See Getting an API key.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
When the key stops working. Left out, or not a date the server can read, the key never expires.
Example
{ "name": "Staging CI", "expires": "2026-12-31T23:59:59Z"}Responses
Section titled “ Responses ”Created. key is the secret, shown only now.
object
The key’s owner.
The workspace the key belongs to; null for an account-wide key.
In UTC, as YYYY-MM-DD HH:MM:SS.
Whether the key is switched on: true/false when just created, 1/0 when listed.
When the key stops working, in UTC, as YYYY-MM-DD HH:MM:SS. null if never.
inactive means switched off.
The full key. Store it now; it isn’t shown again.
Example
{ "id": 42, "name": "Staging CI", "userID": 7, "organizationId": 3, "createdDate": "2026-09-25 14:41:00", "active": true, "expires": "2026-12-31 23:59:59", "lastUsedAt": null, "status": "active", "key": "uk42_<secret>"}Headers
Section titled “Headers”Requests the workspace’s plan allows per minute.
Requests left in the current minute.
Unix time, in seconds, when the current minute ends.
The name is missing or too long.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
Examples
{ "error": true, "code": 400, "message": "Key name is required"}{ "error": true, "code": 400, "message": "Key name too long"}The API key is missing, wrong, expired, switched off, or no longer valid.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
Examples
{ "error": true, "code": 401, "message": "API key is required. Provide X-API-Key header or Authorization: Bearer <key>"}{ "error": true, "code": 401, "message": "Invalid or expired API key"}{ "error": true, "code": 401, "message": "API key has expired"}{ "error": true, "code": 401, "message": "API key owner account is inactive"}{ "error": true, "code": 401, "message": "This API key's workspace access has been removed"}The request used an account-wide key, or the workspace’s plan has no API access.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
Example
{ "error": true, "code": 403, "message": "API access isn't included on this workspace's plan. Upgrade to create API keys."}A usage limit was reached, or this IP address made too many requests with a wrong key.
Usage limits are per workspace and plan: per minute (Retry-After: 60), per day
(Retry-After: 3600), and per month (no Retry-After). They use their own body shape.
An IP address may send 10 wrong keys a minute; one more is allowed every 6 seconds. While
that allowance is used up, every request from the address is refused, even one with a
valid key.
object
Seconds to wait. Absent for the monthly limit.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
Examples
{ "error": "Rate limit exceeded", "message": "Per-minute limit of 60 requests exceeded", "retry_after": 60}{ "error": "Daily limit exceeded", "message": "Daily limit of 25000 requests exceeded", "retry_after": 3600}{ "error": "Monthly limit exceeded", "message": "Monthly limit of 250000 requests exceeded"}{ "error": true, "code": 429, "message": "Too many failed API key attempts from this address. Wait a few seconds and try again.", "retry_after": 6}Headers
Section titled “Headers”Seconds to wait. 60 for the per-minute limit, 3600 for the daily limit, 6 after wrong keys. Absent for the monthly limit.
Something failed on TunnelHQ’s side.
object
The HTTP status, repeated.
What went wrong.
More detail, on some errors.
Seconds to wait, on the 429 for wrong keys.
Example
{ "error": true, "code": 500, "message": "Internal server error"}