api REST API

API Reference

Complete documentation for the Lygo REST APIs. Integrate shortlinks, analytics, and domain management directly into your code.

lock

Authentication

Lygo supports two authentication methods. Choose the one best suited to your use case.

person

Bearer Token (JWT)

For authenticated users. Obtain the token from the admin panel or via POST /api/auth/login.

Authorization: Bearer eyJhbGci...
key

API Key

For service accounts and automations. Create a key from the Service Accounts panel.

x-api-key: slk_xxxxxxxxxxxxxxxx
link

Base URL

All endpoints are relative to the following base URL:

https://app.lygo.it/api

All requests and responses use Content-Type: application/json.

route

Endpoints

POST /api/links Create shortlink

Creates a new shortlink in the specified tenant. Requires editor role or higher, or links:write scope for API keys.

Request Body

{
  "tenantId": "tnt_abc123",              // required
  "originalUrl": "https://example.com/very/long/path", // required, http/https
  "urlCode": "my-link",                  // optional, 3-64 chars: a-zA-Z0-9_-
                                         // 8-char nanoid auto-generated if omitted
  "redirectType": "301",                 // optional, "301"|"302" (string), default "301"
  "domainId": "dom_xyz",                 // optional
  "password": "secret",                  // optional, 4-128 chars
  "redirectQueryParams": { "utm_source": "newsletter" }, // optional
  "activeFrom": "2025-06-01T00:00:00Z",  // optional, ISO 8601
  "activeUntil": "2025-12-31T23:59:59Z"  // optional, ISO 8601
}

Response 201

{
  "tenantId": "tnt_abc123",
  "urlCode": "my-link",
  "originalUrl": "https://example.com/very/long/path",
  "redirectType": "301",                 // always a string
  "isPasswordProtected": false,
  "redirectQueryParams": { "utm_source": "newsletter" },
  "activeFrom": "2025-06-01T00:00:00Z",  // string | null
  "activeUntil": "2025-12-31T23:59:59Z", // string | null
  "domainId": null,
  "domain": null
}
GET /api/links List links (paginated)

Returns a paginated list of the tenant's links. Rate limit: 120 req / 60s per IP.

Query Parameters

?tenantId=tnt_abc123   // required
&paginated=1           // include pagination envelope
&limit=50              // optional, 10-100, default 50
&cursor=base64string   // optional, opaque pagination cursor (base64)
&search=my-link        // optional, filters by urlCode prefix
&redirectType=301      // optional, "301"|"302"
&sort=code             // optional, code|recent|oldest|clicks (default: code)

Response 200

{
  "tenantId": "tnt_abc123",
  "role": "owner",                       // owner|admin|editor|viewer
  "links": [
    {
      "urlCode": "my-link",
      "originalUrl": "https://example.com/very/long/path",
      "redirectType": "301",             // always a string
      "isPasswordProtected": false,
      "redirectQueryParams": {},
      "activeFrom": null,
      "activeUntil": null,
      "clickCount": 42,
      "createdAt": "2025-03-01T12:00:00Z",
      "createdBy": "usr_abc123",
      "domainId": null,
      "domain": null
    }
  ],
  "pagination": {
    "limit": 50,
    "hasMore": true,
    "nextCursor": "eyJ1cmxDb2RlIjoibXktbGluayJ9", // string | null
    "search": null,
    "sort": "code",
    "redirectType": ""
  }
}
GET /api/links/:urlCode Link details

Returns all details for a single link.

Query Parameters

?tenantId=tnt_abc123   // required
&domainId=dom_xyz      // optional
&domain=custom.example.com // optional

Example

GET /api/links/my-link?tenantId=tnt_abc123

Response 200

{
  "urlCode": "my-link",
  "originalUrl": "https://example.com/very/long/path",
  "redirectType": "301",
  "isPasswordProtected": false,
  "redirectQueryParams": {},
  "activeFrom": null,
  "activeUntil": null,
  "clickCount": 42,
  "createdAt": "2025-03-01T12:00:00Z",
  "createdBy": "usr_abc123",
  "domainId": null,
  "domain": null
}
PUT /api/links/:urlCode Update link

Updates an existing link. The urlCode field cannot be changed. At least one field to update must be provided.

Query Parameters

?tenantId=tnt_abc123   // required

Request Body (all fields optional, at least one required)

{
  "originalUrl": "https://new-destination.com",  // optional
  "redirectType": "302",                          // optional, "301"|"302" (string)
  "password": "newsecret",                        // optional, 4-128 chars
  "clearPassword": true,                          // optional, removes the password
  "redirectQueryParams": { "ref": "email" },      // optional
  "clearRedirectQueryParams": true,               // optional, removes query params
  "activeFrom": "2025-06-01T00:00:00Z",           // optional, ISO 8601 | null
  "activeUntil": "2025-12-31T23:59:59Z"           // optional, ISO 8601 | null
}

Response 200

{ "message": "Link updated" }
DELETE /api/links/:urlCode Delete link

Permanently deletes a link. This operation is irreversible.

Query Parameters

?tenantId=tnt_abc123   // required
&domainId=dom_xyz      // optional

Example

DELETE /api/links/my-link?tenantId=tnt_abc123

Response 200

{ "message": "Link deleted" }
speed

Rate Limiting

info

120 requests per 60 seconds per IP

The limit applies per IP address on /api/links* endpoints. When exceeded, the API responds with 429 Too Many Requests.

// Response when rate limit is exceeded
HTTP/1.1 429 Too Many Requests

{ "error": "Too many requests" }
error

Error Responses

All errors follow a consistent format with an error field that describes the problem in human-readable language.

{
  "error": "Link not found"
}
Code Meaning
400 Bad Request — missing parameter, wrong type, or invalid value
401 Unauthorized — JWT token or API key missing, expired, or invalid
403 Forbidden — insufficient scope (e.g., missing links:write) or tenant access denied
404 Not Found — link or resource does not exist
409 Conflict — urlCode already in use within the tenant
429 Too Many Requests — rate limit exceeded (120 req/60s per IP)
503 Service Unavailable — temporary maintenance or service unreachable
code

Code Examples

Create a shortlink with fetch (TypeScript)

// POST /api/links
const response = await fetch('https://app.lygo.it/api/links', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'slk_xxxxxxxxxxxxxxxx',
  },
  body: JSON.stringify({
    tenantId: 'tnt_abc123',
    originalUrl: 'https://example.com/very/long/url',
    redirectType: '301',  // string, not a number
  }),
});

const link = await response.json();
// { urlCode: 'xK8mPqZw', redirectType: '301', ... }
console.log(link.urlCode);

List links with curl

curl -G https://app.lygo.it/api/links \
  -H "x-api-key: slk_xxxxxxxxxxxxxxxx" \
  -d "tenantId=tnt_abc123" \
  -d "paginated=1" \
  -d "limit=50" \
  -d "sort=clicks"

Update a link with curl

curl -X PUT "https://app.lygo.it/api/links/my-link?tenantId=tnt_abc123" \
  -H "x-api-key: slk_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"originalUrl":"https://new-destination.com","redirectType":"302"}'

// Response: { "message": "Link updated" }

Error handling with status check

const res = await fetch('https://app.lygo.it/api/links/my-link?tenantId=tnt_abc123', {
  headers: { 'x-api-key': 'slk_xxxxxxxxxxxxxxxx' },
});

if (!res.ok) {
  const { error } = await res.json();
  // error: "Link not found"
  throw new Error(`API error ${res.status}: ${error}`);
}

const link = await res.json();

Ready to get started?

Create your account and get an API key in under 2 minutes.