ADVANCED
DocsLinks over the API
The link is the core object in Kilo, and five endpoints cover its whole life over HTTP: create it, list your links, read one, update where it points, and retire it. Every example below uses a write-scoped key in $KILO_API_KEY; a read key is enough for the two GET calls.
Create a link
POST /api/links mints a link. For a plain URL link the only required field is default_target, an http(s) URL. You may also pass a custom slug (3–32 base62 characters), a human name (up to 80), a group_id, an expires_at epoch-milliseconds timestamp, and a placement of active (default) or archived.
curl https://qr2r.com/api/links \
-H "Authorization: Bearer $KILO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "default_target": "https://example.com/landing", "name": "Spring flyer" }'A successful create returns 201 with the full link object. The slug is the permanent public handle; default_target is the editable destination behind it:
{
"id": 8231,
"slug": "aA3k9",
"name": "Spring flyer",
"kind": "url",
"data": null,
"default_target": "https://example.com/landing",
"expires_at": null,
"disabled": false,
"override_target": null,
"expired_fallback_url": null,
"group_id": null,
"domain": null,
"rule_count": 0,
"created_at": 1719000000000,
"updated_at": 1719000000000,
"deleted_at": null,
"reclaim_until": null,
"paused_at": null,
"pause_reason": null,
"placement_country": null,
"placement_region": null
}The destination guard
A destination cannot point back to a Kilo-owned host or at another URL shortener - that blocks the redirect-chaining trick abusers use to launder a bad link through a trusted one. Those are rejected as 400 invalid_body. A destination that trips the live safety check is refused with 422 destination_unsafe, before any slug is spent.
List and read
GET /api/links returns your links a page at a time. Narrow it with status (active, archived, paused, or deleted - default active), group_id, and the limit/offset pair (limit defaults to 50, caps at 200). The response carries the page plus account-wide counters:
{
"links": [ /* link objects, newest first */ ],
"limit": 50,
"offset": 0,
"status": "active",
"counts": { "active": 12, "archived": 3, "paused": 0, "deleted": 1 },
"limits": { "active_links_max": 25, "archived_links_max": 100, "reclaim_window_days": 30 }
}Read a single link by slug with GET /api/links/:slug; it returns the same object shape as create. An unknown slug returns 404 not_found. For a link on one of your custom domains, add ?domain=go.acme.com so the right link resolves.
Update where it points
PUT /api/links/:slug takes a partial body - send only the fields you’re changing. The common one is default_target; you can also change the name, move it to a group_id, set expires_at, or archive it with disabled. Editing the destination takes effect on the very next scan, everywhere at once - which is the whole point of routing a printed code through Kilo.
curl -X PUT https://qr2r.com/api/links/aA3k9 \
-H "Authorization: Bearer $KILO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "default_target": "https://example.com/new-landing" }'The call returns the updated link object, with a fresh updated_at.
Retire a link
DELETE /api/links/:slug soft-deletes a link: it stops forwarding and moves to the Deleted tab, but it is not gone.
{ "ok": true, "soft": true }You can bring it back with POST /api/links/:slug/reclaim while it’s inside the reclaim window, or remove it for good with POST /api/links/:slug/purge. Nothing is ever hard-deleted out from under you by a plain delete - the same never-a-dead-end principle the link lifecycle guide describes, expressed as endpoints.
That’s the full object over HTTP. The last API guide covers the two things you’ll most often automate around a link: reading its stats and steering its traffic.
Analytics and rules over the API