Live demo · Sign in with demo@linkora.test / admin@linkora.test · password Password123! · data is fictional and resets every 60 minutes; short links show a preview instead of redirecting.
Skip to content

API reference

Versioned REST API (v1). Full machine-readable spec in OpenAPI 3.1.

Download OpenAPI spec

Authentication

Create a token in Developers → API tokens and choose scopes. Tokens are shown once and only a hash is stored. API access requires a plan that includes it.

curl https://linkora-demo.devorbit.dev/api/v1/me \
  -H "Authorization: Bearer lk_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Create a link

Send an Idempotency-Key header so retries never create duplicates.

curl -X POST https://linkora-demo.devorbit.dev/api/v1/links \
  -H "Authorization: Bearer $LINKORA_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2f7e-campaign-42" \
  -d '{"destination_url":"https://example.com/offer","title":"Offer","tags":["email"],
       "utm":{"utm_source":"newsletter","utm_medium":"email","utm_campaign":"spring"}}'

Endpoints

MethodPathScope
GET/api/v1/me—
GET/api/v1/linkslinks:read
POST/api/v1/linkslinks:write
GET/api/v1/links/{id}links:read
PATCH/api/v1/links/{id}links:write
DELETE/api/v1/links/{id}links:write
POST/api/v1/links/{id}/pause|resume|archive|unarchivelinks:write
GET/api/v1/links/{id}/analyticsanalytics:read
GET/api/v1/analyticsanalytics:read
GET/api/v1/links/{id}/qr?format=png|svgqr:read

Errors

Errors use a consistent JSON envelope with a machine-readable code and the request id (also in the X-Request-Id header).

{ "error": { "code": "plan_limit", "message": "…", "request_id": "c0a8…", "details": { "feature": "links_active", "limit": 25 } } }

Webhook signatures

Each delivery carries Linkora-Signature (t=timestamp,v1=HMAC-SHA256 of "timestamp.body"), Linkora-Event and Linkora-Delivery. Reject old timestamps and de-duplicate delivery ids.

// Node.js verification example
const [t, v1] = header.split(',').map(p => p.split('=')[1]);
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
const valid = fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
// Then de-duplicate on the Linkora-Delivery header before processing.

Rate limits

Requests are limited per token (default 120/minute). Exceeding it returns 429 with Retry-After.