Developers

LinkGate API

Go from zero to your first authenticated API call in a few minutes.

Last updated: 2026-07-04

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, and Content-Type: application/json for 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." } }
StatusWhen
401Missing, invalid, or expired API key.
403Account disabled, or the key lacks the required scope.
404Resource not found (or not owned by you).
422Validation failed — see error.details for per-field messages.
429Rate 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.