Getting started
The LinkGate API is a JSON HTTP API. Every request lives under /api/v1 on
your LinkGate origin. You authenticate with a personal API key tied to your own
account, and every endpoint is scoped to that account — there is no cross-user
access.
- Base URL:
{origin}/api/v1 - Format: JSON. Send
Accept: application/json, andContent-Type: application/jsonfor bodies. - Auth:
Authorization: Bearer <api-key>on every request.
Prefer to browse endpoints interactively? See the interactive reference —
it is generated from our OpenAPI spec, which you can also download at
/openapi.yaml.
From key to first call
Two steps: create an API key in your dashboard, then use it.
1. Get an API key
Open Dashboard → API keys, click Create key, give it a name, and copy the value. The plaintext key is shown once only — copy it immediately, it cannot be retrieved again. Only a hash is stored server-side. Keys expire 30 days after issuance, and you can rotate or revoke them from the same screen at any time.
Keep the key somewhere safe (a secrets manager or .env):
export APP_URL="https://linkgate.app"
export API_KEY="paste-your-key-here"
2. Make your first call
Send the key as a bearer header. Here we create a short link:
curl -X POST "$APP_URL/api/v1/links" \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-d '{"original_url":"https://example.com","title":"Docs"}'
You get back the created link, including its code and public short_url:
{
"data": {
"id": "h690g9ndk2d32lj79ppfnmgu",
"code": "Gfp4dGK",
"short_url": "https://lg.example/s/Gfp4dGK",
"original_url": "https://example.com",
"title": "Docs",
"is_active": true,
"click_count": 0
}
}
That is the whole loop: create key → authenticated request.
Code samples
The same first call in a few languages. Each reads the key from an API_KEY
environment variable — never hard-code keys into source.
JavaScript (fetch)
const res = await fetch(`${process.env.APP_URL}/api/v1/links`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({ original_url: 'https://example.com', title: 'Docs' }),
});
const { data } = await res.json();
console.log(data.short_url);
Python (requests)
import os, requests
res = requests.post(
f"{os.environ['APP_URL']}/api/v1/links",
headers={
"Authorization": f"Bearer {os.environ['API_KEY']}",
"Accept": "application/json",
},
json={"original_url": "https://example.com", "title": "Docs"},
)
print(res.json()["data"]["short_url"])
PHP (curl)
$ch = curl_init("{$appUrl}/api/v1/links");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$apiKey}",
"Content-Type: application/json",
"Accept: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"original_url" => "https://example.com",
"title" => "Docs",
]),
]);
$data = json_decode(curl_exec($ch), true)["data"];
echo $data["short_url"];
Rate limits
Authenticated requests are throttled per account. The default tier allows 120 requests per minute; the ceiling rises on higher plans.
When you exceed the limit you receive 429 Too Many Requests with a
Retry-After header telling you how many seconds to wait. Back off until then,
and reuse a single key rather than creating a new one per call.
Errors
Errors return a JSON body with an error.code and a human-readable
error.message:
{ "error": { "code": "E_LINK_NOT_FOUND", "message": "Link not found." } }
| Status | When |
|---|---|
401 | Missing, invalid, or expired API key. |
403 | Account disabled, or the key lacks the required scope. |
404 | Resource not found (or not owned by you). |
422 | Validation failed — see error.details for per-field messages. |
429 | Rate limit exceeded. |
Every error carries an optional error.details. For validation errors (422,
code: "E_VALIDATION") it is an array of { field, rule, message }; otherwise
it is null:
{
"error": {
"code": "E_VALIDATION",
"message": "The request is invalid.",
"details": [
{ "field": "original_url", "rule": "url", "message": "The original_url field must be a valid URL." }
]
}
}
Endpoint reference
The full, always-current endpoint list — parameters, request/response schemas, and a live "try it" console — lives in the interactive reference.