Links
A link is a mapping: a slug on a domain, a fallback url, and an ordered list of routing rules that decide per visitor.
/v1/linksCreate a link
The one required field is url. Everything else — slug, domain, routing, utm — has a working default, so the minimum viable create is one field.
urlstringREQUIREDFallback destination — where visitors go when no rule matches.
slugstringPath on the domain. Omitted → a short random slug, collision-checked.
domainstringDefaults to rii.link. Otherwise a host connected through POST /v1/domains — active, or still pending_dns, in which case the link is staged (see below). Dashboard-connected domains are refused with 400.
routingarrayOrdered if/then rules, up to 50 — see PATCH below for the shape. Needs Premium.
expires_attimestampAfter this moment the link answers 410 instead of routing.
utmobjectCampaign tags written onto url as utm_* parameters: source, medium, campaign, term, content (all optional strings, up to 255 characters). Values are trimmed; source and medium are lowercased; a utm_* already on url is replaced, never duplicated. Routing rule destinations (to) are left as they are. The response returns the tagged url plus a utm object.
/v1/linksList links
Links created through the API or MCP, cursor-paginated, newest first. Filter to one domain to audit a host before disconnecting it.
limitintegerDefault 25, max 100.
cursorstringnext_cursor from the previous page.
domainstringOnly links on this host.
/v1/links/{id}Retrieve a link
The full object, including the routing array in evaluation order.
/v1/links/{id}Update a link
PATCH semantics per field — but routing replaces the whole array on write. Send it in the order you want it evaluated; first match wins.
routingarrayThe full rule list, one condition per rule: country (ISO code), device (ios, android, mobile, tablet, desktop), language (e.g. de), or time — { "days": ["mon", …], "start": "09:00", "end": "17:00", "timezone": "Europe/Berlin" }. First match wins.
urlstringNew fallback destination.
slugstringRenames the path — the old slug 301s for 30 days.
expires_attimestampSet, move, or null it while the link is live — once expired, a link answers 410.
utmobjectChanges only the keys you send: a value sets that utm_* on the destination, null (or "") removes it, omitted keys stay. "utm": null strips every UTM parameter. If url is sent too, the tags go onto the new url. Rule destinations are untouched.
Campaign tags (UTM)
Every link response carries utm — the five UTM parameters on url, exactly as analytics records them, null for any that are absent. Send utm on create or update instead of editing the query string by hand: rii.link writes the tags onto url with one set of rules (the same as the dashboard builder and the MCP tools), so "Email" and "email" never split one channel into two rows. When a visitor clicks, analytics records the tags on the destination actually served — after rules and splits — alongside any utm_* on the short URL itself.
SEND "utm": null TO STRIP EVERY UTM PARAMETER FROM THE DESTINATION
Staged links: import before DNS switches
A link can go onto a domain that is still pending_dns. It is stored and loaded into routing straight away, and comes back with "staged": true and "domain_status": "pending_dns". Once the domain's DNS points at rii.link and its certificate is issued, the domain turns active and the link answers — no second call, nothing to re-send. Plan limits, destination safety checks and validation apply exactly as on an active domain. A domain whose setup failed, or one you disconnected, is refused with 400 validation_failed.
MIGRATING? CONNECT THE DOMAIN, IMPORT EVERY LINK, THEN SWITCH DNS — NO 404 WINDOW
/v1/links/{id}Delete a link
Deletes the mapping and quarantines the slug for 30 days before it can be re-claimed — a reprinted poster should never route somewhere new silently.