REST API

A JSON API over HTTPS for links, analytics, QR codes, domains and workspaces. The full machine-readable reference is the OpenAPI 3.1 spec at https://linkapp-staging.funnelkit.com/api/openapi.json.

Base URL

https://linkapp-staging.funnelkit.com/api

Authentication

Send a workspace API key as a Bearer token on every request. Keys start with fks_ and are created in the dashboard under Settings, API keys.

Authorization: Bearer fks_YOUR_KEY

A key acts on the workspace it was created in. A missing or invalid key returns 401.

POST /links

curl -X POST https://linkapp-staging.funnelkit.com/api/links \
  -H "Authorization: Bearer $FKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/pricing",
    "slug": "pricing",
    "tags": ["web"],
    "utm": { "source": "newsletter", "medium": "email" }
  }'

Returns 201 with the link. Only url is required.

FieldTypeNotes
urlstringDestination, http or https. Scanned before the link is created.
slugstring1 to 64 letters, digits, - or _. Random 7 characters if omitted.
domainstringA verified custom domain. Defaults to link-staging.funnelkit.com.
titlestringUp to 200 characters.
tagsstring[]Up to 20 tags.
utmobjectsource, medium, campaign, term, content.
passwordstringPro and up.
expiresAt, expiredUrlstringISO 8601 time and a fallback destination. Pro and up.
geoobjectCountry code to URL, for example {"DE":"https://example.de"}. Pro and up.
iosUrl, androidUrlstringMobile deep links. Team and up.
redirectTypenumber302 (default) or 301.

To create many links at once, POST /links/bulk with {"links":[...]} (up to 50 per request; the CLI splits larger files automatically). The response lists the result for each item.

GET /links

curl "https://linkapp-staging.funnelkit.com/api/links?limit=25&tag=web" \
  -H "Authorization: Bearer $FKS_API_KEY"

Query parameters: q (search), tag, domain, archived, limit (1 to 100, default 25) and cursor. The response is {"links":[...],"nextCursor":"..."}; pass nextCursor back as cursor to get the next page. It is null on the last page. Archiving a link only hides it from lists: it keeps redirecting. To stop a link but keep its analytics, set expiresAt.

GET /links/{id} returns one link. PATCH /links/{id} changes any field except domain. Edits are unlimited on every plan.

curl -X PATCH https://linkapp-staging.funnelkit.com/api/links/LINK_ID \
  -H "Authorization: Bearer $FKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/pricing-2027"}'

GET /links/{id}/stats

curl "https://linkapp-staging.funnelkit.com/api/links/LINK_ID/stats?from=2026-09-01T00:00:00Z&interval=day" \
  -H "Authorization: Bearer $FKS_API_KEY"

Returns totals, a timeseries and a breakdown by country, device, OS, browser, referrer and trigger. from and to are ISO 8601 times (default: the last 30 days) and interval is hour or day. The window is limited to your plan's analytics retention. GET /stats returns the same for the whole workspace plus the top links.

DELETE /links/{id}

curl -X DELETE https://linkapp-staging.funnelkit.com/api/links/LINK_ID \
  -H "Authorization: Bearer $FKS_API_KEY"

Returns {"ok":true}. The short URL stops redirecting within about a minute.

Other endpoints

EndpointWhat it does
GET /links/{id}/qrSVG QR code. size, ecc, and on Pro and up fg/bg colours.
GET /tagsTags with link counts.
GET /stats/exportClick data as CSV. Team and up.
GET /domains, POST /domainsList or add custom domains.
POST /domains/{id}/verifyRe-check DNS and the certificate.
GET /workspaceThe current workspace, plan and usage.

Errors

Every error has the same shape:

{
  "error": {
    "code": "validation_failed",
    "message": "Use 1-64 letters, digits, \"-\" or \"_\"",
    "details": { "field": "slug" }
  }
}

code is stable and safe to branch on; message is for people; details is optional.

StatusCodesMeaning
401unauthorizedMissing or invalid API key.
402plan_limit_reached, feature_not_in_planThe new-link allowance is used up, or the feature needs a higher plan. Existing links keep working.
403forbiddenYour role cannot do this, or the workspace is suspended.
404not_foundNo such link or domain in this workspace.
409slug_taken, conflictThe slug is already used on that domain.
422validation_failed, unsafe_urlThe body is invalid, or the destination failed the safety scan.
429rate_limitedToo many requests. Wait and retry.

Rate limits

Requests per minute, per workspace: 60 on Free, 600 on Pro, 3,000 on Team and 10,000 on Scale. Over the limit you get 429; back off and retry.