Getting started

REST API for managing links, analytics, QR codes, domains, webhooks, and conversions. Authenticate with a workspace API key as a Bearer token (`Authorization: Bearer lnk_…`).

Authentication

Send your API key as a bearer token. Create one in the dashboard under API Keys. Use a test key while you are exploring — a test key works on sandboxed data, never redirects, and cannot affect your analytics or billing.

Every response has the same shape

Success and failure both arrive as JSON with a success boolean. Check it before reading data. Failures carry a stable error.code — every one is listed in the error dictionary.

Versioning

The path carries the major version. To pin behaviour against future breaking changes, send a Ryabi-Version header. An unknown version is refused rather than ignored, so a typo fails loudly instead of silently doing nothing. See the changelog.

Base URL

https://www.ryabils.com/api/v1

Authentication

curl https://www.ryabils.com/api/v1/links \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

The response envelope

{
  "success": true,
  "data": {
    "id": "6f3c1b2a-…",
    "shortUrl": "https://ryabi.ls/spring-sale"
  },
  "meta": {
    "pagination": {
      "page": 1,
      "perPage": 25,
      "total": 1,
      "totalPages": 1
    }
  }
}

A failure

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input",
    "details": {
      "fields": {
        "destinationUrl": [
          "Must be a valid URL"
        ]
      }
    }
  }
}

Links

post/api/v1/links

Create a link

Shorten a URL. Only destinationUrl is required.

Leave slug out and we generate one that is not already taken. Leave domain out and we pick from the shared pool; pass one of your own verified domains to use it instead.

A key created in test mode produces a sandboxed link: it comes back exactly like any other, but it never redirects, so it records no clicks and counts against neither your plan allowance nor your billing.

Send `"include": ["qr"]` to get the link's QR code back with it — inline SVG plus a PNG URL — turning one call into a poster or an email. Off by default.

Body

A JSON body is required.

destinationUrlstringRequired
Where the short link sends people. Must be a public http or https URL — private network addresses and other schemes are refused with UNSAFE_DESTINATION.
campaignIdstring | nullOptional
captchaEnabledbooleanOptional
clickLimitinteger | nullOptional
Stop redirecting after this many clicks.
min 1
consentRequiredbooleanOptional
deepLinkEnabledbooleanOptional
Open the destination in its native app where one exists.
descriptionstring | nullOptional
max length 1000
domainstringOptional
One of your verified domains. Omit it and we pick from the shared pool.
expiresAtstring | nullOptional
After this, the link stops redirecting.
fallbackUrlstring | nullOptional
Used when the destination is unreachable.
folderIdstring | nullOptional
includearray of stringOptional
Extras to return alongside the new link. `qr` attaches the QR code as inline SVG plus a PNG URL. Off by default.
ogDescriptionstring | nullOptional
max length 500
ogImageUrlstring | nullOptional
ogTitlestring | nullOptional
max length 255
passwordstring | nullOptional
Visitors must enter this before being redirected.
min length 4max length 100
publicStatsEnabledbooleanOptional
Expose a public stats page at /{slug}/stats.
redirectStatusintegerOptional
301 permanent, 302 temporary, 307 temporary preserving the method, 308 permanent preserving the method. Defaults to 302 — a 301 is cached by browsers and is very hard to take back.
301302307308
slugstringOptional
The part after the domain. Leave it out and we generate an unused one. Slugs are unique per domain, not globally, so the same slug can exist on two of your domains.
tagsarray of stringOptional
Free-form labels. A key with a tag scope may only create links carrying one of its tags.
max items 20
titlestring | nullOptional
max length 255
twitterCardstring | nullOptional
summarysummary_large_imageplayer
uniqueIpLimitinteger | nullOptional
min 1
utmCampaignstring | nullOptional
max length 255
utmContentstring | nullOptional
max length 255
utmMediumstring | nullOptional
max length 255
utmSourcestring | nullOptional
max length 255
utmTermstring | nullOptional
max length 255

Errors

  • 400Error
  • 401Error
  • 403Error
  • 409Error
  • 422Error
Every error code, and what to do about each one

Request

curl -X POST https://www.ryabils.com/api/v1/links \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"include":["qr"],"destinationUrl":"https://example.com/spring-sale","slug":"spring-sale"}'

Response · 200

{
  "success": true,
  "data": {
    "id": "6f3c1b2a-1111-4222-8333-444455556666",
    "slug": "spring-sale",
    "domain": "ryabi.ls",
    "destinationUrl": "https://example.com/spring-sale",
    "shortUrl": "https://ryabi.ls/spring-sale",
    "title": "string",
    "description": "string",
    "tags": [
      "string"
    ],
    "totalClicks": 0,
    "uniqueClicks": 0,
    "isActive": true,
    "isArchived": false,
    "isTest": false,
    "expiresAt": "2026-08-20T12:00:00Z",
    "createdAt": "2026-08-20T12:00:00Z",
    "qr": {
      "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" ...>",
      "png": "https://www.ryabils.com/api/v1/qr/render?data=https%3A%2F%2Fryabi.ls%2Fspring-sale&size=512"
    }
  },
  "meta": {
    "pagination": {
      "page": 1,
      "perPage": 1,
      "total": 1,
      "totalPages": 1
    }
  }
}
post/api/v1/links/bulk

Bulk-create links

Body

A JSON body is required.

linksarray of CreateLinkRequired

Request

curl -X POST https://www.ryabils.com/api/v1/links/bulk \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"links":[{"include":["qr"],"destinationUrl":"https://example.com/spring-sale","slug":"spring-sale","domain":"string","title":"string","description":"string","tags":["string"],"folderId":"6f3c1b2a-1111-4222-8333-444455556666","campaignId":"6f3c1b2a-1111-4222-8333-444455556666","redirectStatus":301,"fallbackUrl":"https://example.com/landing","password":"string","expiresAt":"2026-08-20T12:00:00Z","clickLimit":1,"uniqueIpLimit":1,"publicStatsEnabled":true,"utmSource":"string","utmMedium":"string","utmCampaign":"string","utmTerm":"string","utmContent":"string","ogTitle":"string","ogDescription":"string","ogImageUrl":"https://example.com/landing","twitterCard":"summary","deepLinkEnabled":true,"captchaEnabled":true,"consentRequired":true}]}'

Response · 200

OK

Analytics

get/api/v1/analytics/overview

Workspace analytics overview

Request

curl https://www.ryabils.com/api/v1/analytics/overview \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK
get/api/v1/analytics/timeline

Clicks over time

Query parameters

fromstringOptional
linkIdstringOptional
tostringOptional

Request

curl https://www.ryabils.com/api/v1/analytics/timeline \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK

QR

get/api/v1/qr/{linkId}

Generate a QR code (PNG/SVG/PDF)

Path parameters

linkIdstringRequired

Query parameters

formatstringOptional
pngsvgpdf
sizeintegerOptional

Request

curl https://www.ryabils.com/api/v1/qr/{linkId} \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

Image

Domains

get/api/v1/domains

List custom domains

Request

curl https://www.ryabils.com/api/v1/domains \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

{
  "success": true,
  "data": [
    {
      "id": "string",
      "domain": "string",
      "verificationStatus": "string",
      "sslStatus": "string"
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "perPage": 1,
      "total": 1,
      "totalPages": 1
    }
  }
}

Webhooks

get/api/v1/webhooks

List webhook endpoints

Request

curl https://www.ryabils.com/api/v1/webhooks \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

{
  "success": true,
  "data": [
    {
      "id": "string",
      "name": "string",
      "url": "https://example.com/landing",
      "events": [],
      "format": "standard",
      "mode": "live",
      "isActive": true,
      "failureCount": 1,
      "disabledReason": "string",
      "customHeaderNames": [],
      "filters": {
        "match": "all"
      },
      "transform": {
        "kind": "template"
      },
      "batchEnabled": true,
      "batchWindowSeconds": 1,
      "maxConcurrent": 1,
      "mtlsEnabled": true,
      "previousSecretExpiresAt": "2026-08-20T12:00:00Z"
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "perPage": 1,
      "total": 1,
      "totalPages": 1
    }
  }
}
get/api/v1/webhooks/{id}/deliveries

List deliveries, with filtering, search and a live cursor

Pass `since` (an ISO timestamp) to poll for anything newer — that is how the dashboard's live log works. `status=failed` covers both a delivery still retrying and one that gave up.

Path parameters

idstringRequired

Query parameters

eventstringOptional
fromstringOptional
limitintegerOptional
max 200default 50
qstringOptional
Searches the payload, the response body, the event and the idempotency key.
min length 2
sincestringOptional
statusstringOptional
allpendingsuccessfaileddead
tostringOptional

Request

curl https://www.ryabils.com/api/v1/webhooks/{id}/deliveries?limit=50 \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK
get/api/v1/webhooks/{id}/deliveries/{deliveryId}

One delivery, with the exact request and response

Request headers come back with credentials and signatures redacted to a hint — the value never leaves the server.

Path parameters

deliveryIdstringRequired
idstringRequired

Request

curl https://www.ryabils.com/api/v1/webhooks/{id}/deliveries/{deliveryId} \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

{
  "id": "string",
  "event": "string",
  "status": "pending",
  "responseStatus": 1,
  "duration": 1,
  "attempt": 1,
  "maxAttempts": 1,
  "batchSize": 1,
  "nextAttemptAt": "2026-08-20T12:00:00Z",
  "deliveredAt": "2026-08-20T12:00:00Z",
  "errorMessage": "string",
  "replayOfId": "string",
  "replayable": true
}
post/api/v1/webhooks/{id}/deliveries/{deliveryId}/replay

Send one delivery again

Creates a NEW delivery with a NEW idempotency key, never a further attempt at the old one — so a receiver de-duplicating on the key does not silently ignore it. Refused with 409 while the endpoint is paused.

Path parameters

deliveryIdstringRequired
idstringRequired

Request

curl -X POST https://www.ryabils.com/api/v1/webhooks/{id}/deliveries/{deliveryId}/replay \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK
post/api/v1/webhooks/{id}/replay-all

Re-queue the dead letter queue

Queues up to 200 dead deliveries, oldest first, and answers with how many are left. Capped on purpose: firing thousands at a server that has only just come back up is the thundering herd the concurrency limit exists to prevent.

Path parameters

idstringRequired

Request

curl -X POST https://www.ryabils.com/api/v1/webhooks/{id}/replay-all \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK
post/api/v1/webhooks/{id}/rotate-secret

New signing secret

By default the old secret keeps working for 24 hours and both signatures ride in `x-ryabi-signature`, so a receiver can be updated without dropping events. Send `{ "grace": false }` when the secret LEAKED — then the old one stops immediately. The secret is returned once and never again.

Path parameters

idstringRequired

Request

curl -X POST https://www.ryabils.com/api/v1/webhooks/{id}/rotate-secret \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK
post/api/v1/webhooks/{id}/test

Send a test ping

Goes through the same sender as a real delivery, so a green test proves the signature, the custom headers and the mutual TLS handshake your real events will use. Ignores the endpoint's filter, template and batching — you are testing the connection, not the routing. Never leaves a retry scheduled.

Path parameters

idstringRequired

Request

curl -X POST https://www.ryabils.com/api/v1/webhooks/{id}/test \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK
get/api/v1/webhooks/info

Delivery facts — outbound IPs, retry schedule, timeout

`egressIpsConfigured` is false until an operator pins static outbound addresses. Do not write a firewall rule against an empty list.

Request

curl https://www.ryabils.com/api/v1/webhooks/info \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK
get/api/v1/webhooks/types

Download the event payload types

TypeScript interfaces or Go structs, generated from the live event catalogue so they cannot be stale.

Query parameters

langstringOptional
tsgodefault ts

Request

curl https://www.ryabils.com/api/v1/webhooks/types?lang=ts \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

A source file
get/api/v1/webhooks/cli/sessions

List open forwarding sessions

Request

curl https://www.ryabils.com/api/v1/webhooks/cli/sessions \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK
post/api/v1/webhooks/cli/sessions

Open a localhost forwarding session

What `ryabi listen` calls. Returns a session id and a signing secret. Every connection is outbound from your machine, so there is no tunnel and no port to open.

Request

curl -X POST https://www.ryabils.com/api/v1/webhooks/cli/sessions \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK
get/api/v1/webhooks/cli/events

Poll a forwarding session for events

Waits a few seconds for the first event, then answers. Events are removed as they are handed over, so a second poll never repeats them. The poll IS the heartbeat — stop polling and the session expires.

Query parameters

sessionstringRequired

Request

curl https://www.ryabils.com/api/v1/webhooks/cli/events?session= \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK

API Keys

get/api/v1/api-keys

List API keys

Request

curl https://www.ryabils.com/api/v1/api-keys \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

{
  "success": true,
  "data": [
    {
      "id": "string",
      "name": "string",
      "keyPrefix": "string",
      "mode": "live",
      "permissions": [],
      "allowedIps": [],
      "scopeFilters": {},
      "schedule": {},
      "monthlyCallCap": 1,
      "rateLimitOverride": 1,
      "expiresAt": "2026-08-20T12:00:00Z",
      "lastUsedAt": "2026-08-20T12:00:00Z",
      "lastUsedIp": "string",
      "pausedAt": "2026-08-20T12:00:00Z",
      "pausedReason": "string",
      "revokedAt": "2026-08-20T12:00:00Z",
      "revokedReason": "string",
      "rotating": true,
      "graceHoursRemaining": 1,
      "createdAt": "2026-08-20T12:00:00Z"
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "perPage": 1,
      "total": 1,
      "totalPages": 1
    }
  }
}

Conversions

post/api/v1/conversions

Record a conversion (public beacon, no auth)

Body

The body is optional.

clickIdstringOptional
Value of the __ls_click cookie
currencystringOptional
min length 3max length 3
eventNamestringOptional
default conversion
metadataobjectOptional
valuenumberOptional

Request

curl -X POST https://www.ryabils.com/api/v1/conversions \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 201

Created

Pages

get/api/v1/bridge-pages

List bridge pages

Request

curl https://www.ryabils.com/api/v1/bridge-pages \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK
post/api/v1/bridge-pages

Create a page (optionally from a template)

Body

The body is optional.

savedTemplateIdstringOptional
slugstringOptional
templateSlugstringOptional
Seed content from a registry template
titlestringOptional

Request

curl -X POST https://www.ryabils.com/api/v1/bridge-pages \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

Created
get/api/v1/bridge-pages/{pageId}/blocks

List a page's blocks, flat and in reading order

Path parameters

pageIdstringRequired

Request

curl https://www.ryabils.com/api/v1/bridge-pages/{pageId}/blocks \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK
post/api/v1/bridge-pages/{pageId}/blocks

Append a block

Starts from the block type's defaults, then overlays `fields`. `id` and `type` inside `fields` are ignored. Layout is preserved; an empty page gets one full-width section.

Path parameters

pageIdstringRequired

Body

The body is optional.

typestringRequired
A block type, e.g. link_button
fieldsobjectOptional

Request

curl -X POST https://www.ryabils.com/api/v1/bridge-pages/{pageId}/blocks \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"string"}'

Response · 200

Created
get/api/v1/bridge-pages/{pageId}/blocks/{blockId}

Read one block

Path parameters

blockIdstringRequired
pageIdstringRequired

Request

curl https://www.ryabils.com/api/v1/bridge-pages/{pageId}/blocks/{blockId} \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK
patch/api/v1/bridge-pages/{pageId}/blocks/{blockId}

Update one block's fields

Merges the body into the block. Changing `type` is refused with a 400.

Path parameters

blockIdstringRequired
pageIdstringRequired

Request

curl -X PATCH https://www.ryabils.com/api/v1/bridge-pages/{pageId}/blocks/{blockId} \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

Updated
delete/api/v1/bridge-pages/{pageId}/blocks/{blockId}

Delete one block

Path parameters

blockIdstringRequired
pageIdstringRequired

Request

curl -X DELETE https://www.ryabils.com/api/v1/bridge-pages/{pageId}/blocks/{blockId} \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

Deleted

Automation

get/api/v1/zapier/subscriptions

List automation subscriptions

Request

curl https://www.ryabils.com/api/v1/zapier/subscriptions \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK
post/api/v1/zapier/subscriptions

Subscribe a Zap / scenario to an event (REST hook)

Called when an automation is switched ON. Accepts `hookUrl` (Zapier) or `targetUrl` (Make); https only. Deliveries reuse the signed, retried webhook pipeline.

Body

The body is optional.

eventstringRequired
One of the published webhook events
hookUrlstringOptional
targetUrlstringOptional

Request

curl -X POST https://www.ryabils.com/api/v1/zapier/subscriptions \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event":"string"}'

Response · 200

Subscribed
delete/api/v1/zapier/subscriptions/{id}

Unsubscribe (idempotent)

Path parameters

idstringRequired

Request

curl -X DELETE https://www.ryabils.com/api/v1/zapier/subscriptions/{id} \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

Deleted
get/api/v1/zapier/samples

Sample event data for an automation's field mapper

Returns your most recent real delivery of the event when there is one, else a synthetic example flagged `sample: true`. Omit `event` for the whole catalogue.

Query parameters

eventstringOptional
flatstringOptional
Flatten nested keys to parent__child
1

Request

curl https://www.ryabils.com/api/v1/zapier/samples \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK

Account

get/api/v1/me

Identity / auth test

Returns the authenticated user + workspace. Used by integrations (Zapier/Make/n8n) to validate an API key.

Request

curl https://www.ryabils.com/api/v1/me \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK

Templates

get/api/v1/templates

List templates (public, no auth)

Query parameters

categorystringOptional
limitintegerOptional
max 100default 50
offsetintegerOptional
default 0
qstringOptional
Matches name, description and tags.
tagstringOptional

Request

curl https://www.ryabils.com/api/v1/templates?limit=50&offset=0 \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK
get/api/v1/templates/{slug}

One template, including the page tree it builds (public, no auth)

Path parameters

slugstringRequired

Request

curl https://www.ryabils.com/api/v1/templates/{slug} \
  -H "Authorization: Bearer lnk_live_YOUR_API_KEY"

Response · 200

OK