Reference

API reference

A JSON API for everything in the dashboard: links, analytics, plans, API keys and workspaces.

On this page

Introduction

  • Base URL: https://furl.io/api/v1
  • JSON only. Request bodies need Content-Type: application/json and may be at most 64 KB.
  • Unknown JSON fields are rejected with 400 bad_request, so typos don’t pass silently.
  • Timestamps are RFC3339 in UTC, e.g. 2026-10-05T12:34:56Z.

OpenAPI specification

A machine-readable OpenAPI 3.0 description of every endpoint is at /api/v1/openapi.json. Import it into Postman, Insomnia or Swagger Editor, or generate a client with any OpenAPI code generator.

Authentication

Send any one of the following:

MethodHowNotes
Session cookiefurl_session, set by login and registerHttpOnly, Secure, SameSite=Lax, 7 days. Cookie-authenticated POST/PATCH/DELETE requests from another origin are rejected with 403 csrf.
Login tokenAuthorization: Bearer <token>The token returned by login or register. A JWT, valid 7 days.
API keyAuthorization: Bearer furl_… or X-API-Key: furl_…45 characters, starting with furl_. Shown once at creation.
Examples
# API key, either header works
curl -s https://furl.io/api/v1/me -H "Authorization: Bearer $FURL_API_KEY"
curl -s https://furl.io/api/v1/me -H "X-API-Key: $FURL_API_KEY"

# Token from /auth/login or /auth/register (valid 7 days)
curl -s https://furl.io/api/v1/me -H "Authorization: Bearer $FURL_TOKEN"

# Session cookie (what the dashboard uses)
curl -s -c cookies.txt https://furl.io/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","password":"correct horse battery"}'
curl -s -b cookies.txt https://furl.io/api/v1/me

Workspaces and roles

Links, API keys, analytics, the plan and billing belong to a workspace. Every account has a personal workspace; team workspaces are shared with other members. Requests made with a session cookie or login token act on your personal workspace unless you choose another with the X-Furl-Workspace header:

Select a workspace
# List the workspaces you belong to
curl -s https://furl.io/api/v1/workspaces -H "Authorization: Bearer $FURL_TOKEN"

# Act on a team workspace
curl -s https://furl.io/api/v1/links \
  -H "Authorization: Bearer $FURL_TOKEN" \
  -H "X-Furl-Workspace: 9a4d7c1e-2b3f-4e5a-8c6d-0f1e2d3c4b5a"
  • The value is a workspace id from /me or /workspaces. Naming a workspace you don’t belong to returns 403 not_member (except on /me, which falls back to your personal workspace).
  • API keys ignore the header: a key always acts on the workspace it was created in.
RoleCan
memberCreate, edit and delete any link in the workspace; read its analytics.
adminEverything a member can, plus API keys, invitations, members, custom domains and renaming the workspace.
ownerEverything an admin can, plus billing, transferring ownership and deleting the workspace.

Each workspace has exactly one owner. A request your role doesn’t allow returns 403 insufficient_role.

Errors

Errors return a JSON body with a stable, machine-readable code and a human-readable message. Match on the code; the message may change.

409 Conflict
{
  "error": {
    "code": "code_taken",
    "message": "short code already taken"
  }
}
StatusCodeMeaning
400bad_requestMalformed JSON, unknown fields, or invalid query parameters.
401unauthorizedMissing, invalid or expired credentials.
401invalid_credentialsWrong email or password (login).
403plan_limit_reachedYour plan’s active link limit is reached (creating or resuming a link).
403custom_code_limitYour plan’s custom code allowance is used up.
403link_expiry_not_in_planYour plan doesn’t include expiry dates and view limits (Pro and higher).
403integrations_not_in_planYour plan doesn’t include webhooks and GA4 forwarding (Pro and higher).
403csrfCookie-authenticated write from another origin.
403not_purchasableThe plan or extra custom codes can’t be bought on your account.
403managed_accountYour plan is managed by furl.io, so there’s nothing to pay (checkout).
403admin_accountAdministrator accounts manage the service and can’t create links, API keys or purchases. Use a regular account.
403code_blockedThe custom short code is reserved.
403invalid_passwordWrong current password (account deletion).
403email_unverifiedThe account’s email isn’t verified yet.
403session_requiredNeeds a session or login token, not an API key (account deletion, billing).
403not_memberX-Furl-Workspace names a workspace you don’t belong to.
403insufficient_roleYour role in the workspace doesn’t allow this.
403member_limitEvery seat on the workspace’s plan is taken (members plus open invitations).
403domain_limitThe workspace’s plan doesn’t include (more) custom domains.
403invite_email_mismatchThe invitation was sent to a different email address.
404invite_invalidUnknown, expired, withdrawn or already used invitation.
400signature_mismatchThe payment signature doesn’t match (billing verify).
400payment_mismatchThe payment doesn’t match the order (billing verify).
409payment_not_capturedThe payment hasn’t been captured yet (billing verify).
404not_foundNot found — also returned for links you don’t own.
409code_takenThe custom short code is in use.
409email_takenAn account with this email exists.
409already_verifiedThe email is already verified.
409personal_workspaceNot possible in a personal workspace (rename, delete, invitations).
409owner_requiredThe workspace must keep exactly one owner; transfer ownership first.
409already_memberThe invited person is already a member.
409domain_takenThe custom domain is already added to a workspace.
409domain_in_useLinks still use the custom domain.
409workspace_has_membersThe account owns a team workspace with other members (account deletion). Transfer ownership or delete it first.
410otp_expiredVerification code expired or too many wrong attempts.
422terms_requiredRegistration without accept_terms: true.
422otp_invalidWrong verification code (includes attempts_left).
422validation_errorA value is well-formed but not allowed (see the message).
429otp_rate_limitedVerification code requested too soon (includes retry_after).
429—Too Many Requests (plain-text body, not JSON).
500internalSomething went wrong on our side.
503email_send_failedThe verification email couldn’t be sent.
503email_unavailableEmail sending is unavailable (verification codes, invitations).
503billing_unavailableOnline payments are temporarily unavailable.

Rate limits

Limits are per client IP address. Exceeding them returns 429 with a plain-text body.

TrafficLimitBurst
/api/v1/auth/*10 requests per minute10
Other API endpoints20 requests per second40
Redirects (furl.io/{code})50 requests per second100
Dashboard and docs pages30 requests per second60

Analytics endpoints (/links/{code}/stats* and /stats/overview) also allow at most 4 requests in flight per account; more return 429 too_many_concurrent_requests with Retry-After: 1. Verification emails are subject to a platform-wide budget; when it is exhausted, 503 email_unavailable is returned with Retry-After.

Pagination

List endpoints take limit (default 50, max 200) and cursor. Pass the previous response’s next_cursor as cursor to get the next page. When next_cursor is null, you’ve reached the last page.

Fetch every link
cursor=""
while :; do
  page=$(curl -s "https://furl.io/api/v1/links?limit=200&cursor=$cursor" \
    -H "Authorization: Bearer $FURL_API_KEY")
  echo "$page" | jq -r '.items[].short_url'
  cursor=$(echo "$page" | jq -r '.next_cursor // empty')
  [ -z "$cursor" ] && break
done

Objects

FieldTypeDescription
idintegerNumeric id.
short_codestringThe code after furl.io/.
short_urlstringThe full short URL.
original_urlstringThe destination.
is_custombooleanWhether the code was chosen by the owner.
statusstringactive | paused | archived | banned
http_status_codeinteger301 | 302 | 307 | 308
google_analytics_idstring | nullGA4 measurement ID.
has_ga_secretbooleanWhether a GA4 API secret is saved (the secret is never returned).
webhook_urlstring | nullWebhook endpoint.
webhook_secretstring | nullSigning secret (whsec_…), generated automatically when a webhook URL is first set.
expires_atRFC3339 | nullWhen the link stops redirecting; null = never expires (the default).
max_clicksinteger | nullView limit (1–100,000); null = unlimited.
clicks_usedintegerViews consumed so far. Only counted while max_clicks is set; link previews and HEAD requests don’t count.
created_atRFC3339
updated_atRFC3339
Link
{
  "id": 42,
  "short_code": "launch",
  "short_url": "https://furl.io/launch",
  "original_url": "https://example.com/spring-sale?utm_source=newsletter",
  "is_custom": true,
  "status": "active",
  "http_status_code": 302,
  "google_analytics_id": "G-ABC123XYZ9",
  "has_ga_secret": true,
  "webhook_url": "https://hooks.example.com/furl",
  "webhook_secret": "whsec_9f2c4e1a7b3d5f60a8c2e4b6d8f0a1c3",
  "expires_at": null,
  "max_clicks": null,
  "clicks_used": 0,
  "created_at": "2026-10-01T09:30:00Z",
  "updated_at": "2026-10-05T12:00:00Z"
}

User

tier is your plan’s code (free, pro, enterprise, custom, or the code of a private plan). email_verified is false until the code emailed at registration is confirmed. managed is true when your plan is assigned by furl.io, with nothing to pay. tier, managed, plan and usage describe the current workspace. /me also returns the current workspace and all your workspaces.

User
{
  "id": "3f6c2a9e-5b1d-4c47-9a2e-7d0b8f1e4a63",
  "email": "[email protected]",
  "tier": "free",
  "is_admin": false,
  "managed": false,
  "email_verified": true,
  "email_verification_required": false,
  "created_at": "2026-10-01T09:00:00Z"
}

Plan

FieldTypeDescription
codestringPlan identifier, same as the user’s tier.
namestringDisplay name.
descriptionstringShort description.
price_monthlynumberPrice per month in US dollars, excluding taxes. 0 when price_on_request is true.
price_on_requestbooleanThe plan is arranged with our sales team and can’t be bought online; it has no listed price.
currencystringAlways USD.
max_active_linksinteger | nullHow many links can be active (and not expired) at once; null = unlimited.
link_expirybooleanLinks can be given an expiry date or view limit.
integrationsbooleanLinks can have a webhook and GA4 forwarding, and clicks are forwarded.
custom_codes_includedinteger | nullCustom short codes included; 0 = none, null = unlimited.
custom_code_pricenumber | nullUSD per extra custom code per month; null = not available.
max_membersinteger | nullSeats per workspace (members plus open invitations); null = unlimited.
max_domainsinteger | nullCustom domains per workspace; 0 = not included, null = unlimited.
featuresstring[]Highlights shown on the pricing page.
tax_notestringShown next to the price, e.g. “+ applicable taxes”.
versionintegerIncreases each time the plan’s terms change.
effective_fromRFC3339When this version took effect.
Plan
{
  "code": "pro",
  "name": "Pro",
  "description": "For teams sharing links every day.",
  "price_monthly": 12,
  "price_on_request": false,
  "currency": "USD",
  "max_active_links": 300,
  "link_expiry": true,
  "integrations": true,
  "custom_codes_included": 30,
  "custom_code_price": 1,
  "max_members": 3,
  "max_domains": 1,
  "features": ["Everything in Free", "Webhooks and GA4 forwarding", "Expiry by date or number of clicks", "Email support"],
  "tax_note": "+ applicable taxes",
  "version": 3,
  "effective_from": "2026-10-01T00:00:00Z"
}

ApiKey

key_prefix identifies the key without revealing it. last_used_at is null until first use.

ApiKey
{
  "id": "b7e1d0c2-4f3a-4e8b-9c6d-1a2b3c4d5e6f",
  "name": "ci-deploy",
  "key_prefix": "furl_k3Jd9",
  "is_active": true,
  "created_at": "2026-10-02T08:00:00Z",
  "last_used_at": "2026-10-05T11:58:12Z"
}

Redirect behaviour

A visitor opening https://furl.io/{code} gets:

Link stateResponse
Active301, 302, 307 or 308 (the link’s redirect type) to the destination
Paused, or no such code404 Not Found
Archived, or past expires_at410 Gone
View limit reached (clicks_used ≥ max_clicks)410 Gone (“this link has reached its view limit”)
Banned451 Unavailable For Legal Reasons

For links with a view limit (max_clicks), each redirect increments clicks_used. Link-preview bots (Slack, WhatsApp, Telegram, Discord, Facebook, X/Twitter, LinkedIn…) and HEAD requests get a neutral page instead: it doesn’t consume a view or reveal the destination. View-limited links are never cached by browsers, even with a 301 or 308 redirect type.

Authentication and account

Version

GET/version

Auth: None

The running release. Public; useful for health checks and bug reports.

Response

200 OK
{
  "version": "1.4.0",
  "commit": "9f2c4e1a7b3d5f60a8c2e4b6d8f0a1c3e5f7a9b1"
}

Errors

Plus the common errors.

curl
curl -s https://furl.io/api/v1/version

Register

POST/auth/register

Auth: None

Creates an account on the Free plan and signs it in: the response sets the furl_session cookie and also returns a token. A 6-digit verification code is emailed; verification_email_sent says whether that worked. Confirm it with verify email.

JSON body

NameTypeDescription
emailrequiredstringA valid email address.
passwordrequiredstring8–72 characters.
accept_termsrequiredbooleanMust be true: the user accepts the Terms of Use and Privacy Policy. The accepted version and time are recorded.

Response

201 Created
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": "3f6c2a9e-5b1d-4c47-9a2e-7d0b8f1e4a63",
    "email": "[email protected]",
    "tier": "free",
    "is_admin": false,
    "managed": false,
    "email_verified": false,
    "email_verification_required": false,
    "created_at": "2026-10-01T09:00:00Z"
  },
  "verification_email_sent": true
}

Errors

StatusCodeWhen
409email_takenAn account with this email already exists.
422terms_requiredaccept_terms is missing or not true.
422validation_errorInvalid email, or password outside 8–72 characters.

Plus the common errors.

curl
curl -s https://furl.io/api/v1/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","password":"correct horse battery","accept_terms":true}'

Log in

POST/auth/login

Auth: None

Sets the furl_session cookie and returns a token (a JWT valid for 7 days) you can send as Authorization: Bearer <token>.

JSON body

NameTypeDescription
emailrequiredstringAccount email.
passwordrequiredstringAccount password.

Response

200 OK
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": "3f6c2a9e-5b1d-4c47-9a2e-7d0b8f1e4a63",
    "email": "[email protected]",
    "tier": "free",
    "is_admin": false,
    "managed": false,
    "email_verified": true,
    "email_verification_required": false,
    "created_at": "2026-10-01T09:00:00Z"
  }
}

Errors

StatusCodeWhen
401invalid_credentialsWrong email or password.

Plus the common errors.

curl
curl -s https://furl.io/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","password":"correct horse battery"}'

Verify email

POST/auth/verify-email

Auth: Session cookie or login token

Confirms the 6-digit code emailed at registration. Codes expire after 10 minutes or 5 wrong attempts. When the server enforces verification, unverified users get 403 email_unverified from every endpoint except /me, /auth/* and DELETE /me.

JSON body

NameTypeDescription
coderequiredstringThe 6 digits from the email. Spaces and dashes are tolerated.

Response

200 OK
{
  "id": "3f6c2a9e-5b1d-4c47-9a2e-7d0b8f1e4a63",
  "email": "[email protected]",
  "tier": "free",
  "is_admin": false,
  "managed": false,
  "email_verified": true,
  "email_verification_required": false,
  "created_at": "2026-10-01T09:00:00Z"
}

Errors

StatusCodeWhen
422otp_invalidWrong code. The error object includes attempts_left.
422validation_errorNot 6 digits.
410otp_expiredExpired, or 5 wrong attempts. Request a new code.

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/auth/verify-email \
  -H "Authorization: Bearer $FURL_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"code":"123456"}'

Resend verification code

POST/auth/resend-verification

Auth: Session cookie or login token

Sends a new code (the old one stops working). No body. Limited to one per 60 seconds and five per hour.

Response

202 Accepted
{
  "sent": true,
  "expires_in": 600
}

Errors

StatusCodeWhen
429otp_rate_limitedToo soon. The error object includes retry_after (seconds); the Retry-After header is set too.
409already_verifiedThe email is already verified.
503email_send_failedThe email provider rejected or failed the send. Try again.
503email_unavailableEmail sending isn’t configured or is temporarily unavailable.

Plus the common errors (including 401 unauthorized).

curl
curl -s -X POST https://furl.io/api/v1/auth/resend-verification \
  -H "Authorization: Bearer $FURL_TOKEN"

Log out

POST/auth/logout

Auth: None

Clears the furl_session cookie. Login tokens can’t be revoked; they expire after 7 days, so discard them on your side.

Response

204 No Content with an empty body.

Errors

Plus the common errors.

curl
curl -s -X POST https://furl.io/api/v1/auth/logout -b cookies.txt -c cookies.txt

Current user

GET/me

Auth: Session cookie, login token or API key (details)

Returns the authenticated user, the current workspace (chosen with X-Furl-Workspace; if that names a workspace you’re no longer in, your personal workspace), every workspace you belong to in workspaces (personal first), and the current workspace’s plan (see Plan) and usage: active_links (links that are active and not expired), custom_codes (links created with a custom code) and custom_code_addon (extra custom codes added to the plan). Custom codes allowed = plan.custom_codes_included + usage.custom_code_addon, or unlimited when custom_codes_included is null.

Response

200 OK
{
  "id": "3f6c2a9e-5b1d-4c47-9a2e-7d0b8f1e4a63",
  "email": "[email protected]",
  "tier": "pro",
  "is_admin": false,
  "managed": false,
  "email_verified": true,
  "email_verification_required": false,
  "created_at": "2026-10-01T09:00:00Z",
  "workspace": {
    "id": "9a4d7c1e-2b3f-4e5a-8c6d-0f1e2d3c4b5a",
    "name": "Acme Marketing",
    "slug": "acme-marketing",
    "personal": false,
    "role": "owner",
    "tier": "pro",
    "managed": false,
    "custom_code_addon": 0,
    "plan_paid_until": "2027-01-04T10:00:00Z",
    "created_at": "2026-10-02T08:00:00Z"
  },
  "workspaces": [
    {
      "id": "5c2e8f0a-7d1b-4a3c-9e6f-2b4d6f8a0c1e",
      "name": "Personal",
      "slug": "personal-3f6c2a9e",
      "personal": true,
      "role": "owner",
      "tier": "free",
      "managed": false,
      "custom_code_addon": 0,
      "plan_paid_until": null,
      "created_at": "2026-10-01T09:00:00Z"
    },
    {
      "id": "9a4d7c1e-2b3f-4e5a-8c6d-0f1e2d3c4b5a",
      "name": "Acme Marketing",
      "slug": "acme-marketing",
      "personal": false,
      "role": "owner",
      "tier": "pro",
      "managed": false,
      "custom_code_addon": 0,
      "plan_paid_until": "2027-01-04T10:00:00Z",
      "created_at": "2026-10-02T08:00:00Z"
    }
  ],
  "plan": {
    "code": "pro",
    "name": "Pro",
    "description": "For teams sharing links every day.",
    "price_monthly": 12,
    "price_on_request": false,
    "currency": "USD",
    "max_active_links": 300,
    "link_expiry": true,
    "integrations": true,
    "custom_codes_included": 30,
    "custom_code_price": 1,
    "max_members": 3,
    "max_domains": 1,
    "features": ["Everything in Free", "Webhooks and GA4 forwarding", "Expiry by date or number of clicks", "Email support"],
    "tax_note": "+ applicable taxes",
    "version": 3,
    "effective_from": "2026-10-01T00:00:00Z"
  },
  "usage": {
    "active_links": 42,
    "custom_codes": 3,
    "custom_code_addon": 0
  }
}

Errors

StatusCodeWhen
401unauthorizedMissing, invalid or expired credentials.

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/me \
  -H "Authorization: Bearer $FURL_API_KEY"

Delete account

DELETE/me

Auth: Session cookie or login token only. API keys are refused (403 session_required).

Permanently erases the account, its personal workspace and any workspace where it’s the only member, with their links (which stop redirecting immediately), API keys, webhook delivery logs and the links' click analytics. Links the account created in shared team workspaces stay with the team. Some records are kept as the law requires; see the Privacy Policy.

JSON body

NameTypeDescription
passwordrequiredstringThe account’s current password.

Response

204 No Content with an empty body.

Errors

StatusCodeWhen
403invalid_passwordThe password is wrong.
403session_requiredThe request used an API key.
409workspace_has_membersThe account owns a team workspace with other members. Transfer ownership or delete that workspace first.

Plus the common errors (including 401 unauthorized).

curl
curl -s -X DELETE https://furl.io/api/v1/me \
  -H "Authorization: Bearer $FURL_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"password":"correct horse battery"}'

Plans

List plans

GET/plans

Auth: None

The plans on the pricing page, in display order. Public; no authentication needed. Your own plan, with your usage, is on /me.

Response

200 OK
{
  "items": [
    {
      "code": "pro",
      "name": "Pro",
      "description": "For teams sharing links every day.",
      "price_monthly": 12,
      "price_on_request": false,
      "currency": "USD",
      "max_active_links": 300,
      "link_expiry": true,
      "integrations": true,
      "custom_codes_included": 30,
      "custom_code_price": 1,
      "max_members": 3,
      "max_domains": 1,
      "features": ["Everything in Free", "Webhooks and GA4 forwarding", "Expiry by date or number of clicks", "Email support"],
      "tax_note": "+ applicable taxes",
      "version": 3,
      "effective_from": "2026-10-01T00:00:00Z"
    }
  ]
}

Errors

Plus the common errors.

curl
curl -s https://furl.io/api/v1/plans

Billing

Pay for a plan or for extra custom codes with Razorpay Standard Checkout. Amounts are in cents (US dollars). The price is the plan price (or the per-code price × quantity) × months, plus 18% GST when billing_country is IN. Periods are prepaid 30-day months with no automatic renewal: paying for the same plan or quantity again extends from the current end date, and switching plans starts now. When a period ends the workspace returns to the Free plan (or the extra codes to 0). Billing applies to the current workspace; buying needs the owner role (403 insufficient_role otherwise), and the history lists the workspace’s orders. These endpoints need a signed-in session; API keys are refused.

Create an order

POST/billing/orders

Auth: Session cookie or login token only. API keys are refused (403 session_required).

Creates a Razorpay order and returns what Razorpay Checkout needs (key_id, order_id, amount, currency, name, description, prefill) plus the price breakdown. Open the checkout with these values, then send its result to verify.

JSON body

NameTypeDescription
kindrequiredstringplan or custom_codes.
plan_codestringWith kind: plan: the plan to buy (a paid public plan).
quantityintegerWith kind: custom_codes: number of extra custom codes, 1–100,000.
monthsrequiredintegerPeriod to pay for, 1–12 (30-day months).
billing_countryrequiredstringISO 3166-1 alpha-2 country code, e.g. IN or US.
gstinstringOptional GSTIN; only with billing_country: IN.

Response

201 Created
{
  "order_id": "order_PQ7sYdK2aB3cDe",
  "amount": 6726,
  "currency": "USD",
  "key_id": "rzp_live_XXXXXXXXXXXXXX",
  "name": "furl.io",
  "description": "Pro plan · 3 months",
  "prefill": { "email": "[email protected]" },
  "breakdown": {
    "unit_price": 1900,
    "quantity": 1,
    "months": 3,
    "subtotal": 5700,
    "tax_rate": 18,
    "tax": 1026,
    "total": 6726,
    "currency": "USD",
    "tax_label": "GST 18%"
  }
}

Errors

StatusCodeWhen
403not_purchasableThe plan can’t be bought online (the Free plan, or a plan with price_on_request, which is arranged with our sales team), or extra custom codes aren’t offered on your plan.
403managed_accountYour plan is managed by furl.io, so there’s nothing to pay.
403insufficient_roleOnly the workspace owner can pay.
403session_requiredThe request used an API key.
422validation_errorA missing or invalid field (months, quantity, country, GSTIN…).
503billing_unavailableOnline payments are temporarily unavailable.

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/billing/orders \
  -H "Authorization: Bearer $FURL_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"kind":"plan","plan_code":"pro","months":3,"billing_country":"IN"}'

Verify a payment

POST/billing/verify

Auth: Session cookie or login token only. API keys are refused (403 session_required).

Send the three values Razorpay Checkout passes to its success handler. When the signature and payment check out, the order is marked paid and the plan or extra codes take effect immediately.

JSON body

NameTypeDescription
razorpay_order_idrequiredstringFrom the checkout result.
razorpay_payment_idrequiredstringFrom the checkout result.
razorpay_signaturerequiredstringFrom the checkout result.

Response

200 OK
{
  "id": "ord_01J9Z6Q4X1",
  "status": "paid",
  "kind": "plan",
  "plan_code": "pro",
  "quantity": null,
  "months": 3,
  "razorpay_order_id": "order_PQ7sYdK2aB3cDe",
  "razorpay_payment_id": "pay_PQ7tA1b2C3d4E5",
  "billing_country": "IN",
  "gstin": null,
  "breakdown": {
    "unit_price": 1900,
    "quantity": 1,
    "months": 3,
    "subtotal": 5700,
    "tax_rate": 18,
    "tax": 1026,
    "total": 6726,
    "currency": "USD",
    "tax_label": "GST 18%"
  },
  "period_start": "2026-10-06T10:00:00Z",
  "period_end": "2027-01-04T10:00:00Z",
  "created_at": "2026-10-06T09:58:41Z",
  "paid_at": "2026-10-06T10:00:00Z"
}

Errors

StatusCodeWhen
400signature_mismatchThe signature doesn’t match the order and payment.
400payment_mismatchThe payment doesn’t belong to this order or the amount differs.
404not_foundNo such order on your account.
409payment_not_capturedThe payment hasn’t been captured yet. Try again shortly.
503billing_unavailableOnline payments are temporarily unavailable.

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/billing/verify \
  -H "Authorization: Bearer $FURL_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"razorpay_order_id":"order_PQ7sYdK2aB3cDe","razorpay_payment_id":"pay_PQ7tA1b2C3d4E5","razorpay_signature":"9d1c…"}'

Billing history

GET/billing/orders

Auth: Session cookie or login token only. API keys are refused (403 session_required).

Your orders, newest first (status is created, paid or failed), with the end of the current paid plan period (plan_paid_until) and extra-codes period (addon_paid_until), and whether online payments are available (payments_enabled).

Response

200 OK
{
  "items": [
    {
      "id": "ord_01J9Z6Q4X1",
      "status": "paid",
      "kind": "plan",
      "plan_code": "pro",
      "quantity": null,
      "months": 3,
      "razorpay_order_id": "order_PQ7sYdK2aB3cDe",
      "razorpay_payment_id": "pay_PQ7tA1b2C3d4E5",
      "billing_country": "IN",
      "gstin": null,
      "breakdown": {
        "unit_price": 1900,
        "quantity": 1,
        "months": 3,
        "subtotal": 5700,
        "tax_rate": 18,
        "tax": 1026,
        "total": 6726,
        "currency": "USD",
        "tax_label": "GST 18%"
      },
      "period_start": "2026-10-06T10:00:00Z",
      "period_end": "2027-01-04T10:00:00Z",
      "created_at": "2026-10-06T09:58:41Z",
      "paid_at": "2026-10-06T10:00:00Z"
    }
  ],
  "plan_paid_until": "2027-01-04T10:00:00Z",
  "addon_paid_until": null,
  "payments_enabled": true
}

Errors

StatusCodeWhen
403session_requiredThe request used an API key.

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/billing/orders \
  -H "Authorization: Bearer $FURL_TOKEN"

Rotate the webhook secret

POST/links/{code}/webhook/rotate-secret

Auth: Session cookie, login token or API key (details)

Generates a new webhook_secret and returns the updated link. The old secret stops working immediately. Works on every plan while the link has a webhook.

Path parameters

NameTypeDescription
coderequiredstringThe link’s short code (case-sensitive). URL-encode it if it contains special characters.

Response

200 OK
{
  "id": 42,
  "short_code": "launch",
  "short_url": "https://furl.io/launch",
  "original_url": "https://example.com/spring-sale?utm_source=newsletter",
  "is_custom": true,
  "status": "active",
  "http_status_code": 302,
  "google_analytics_id": "G-ABC123XYZ9",
  "has_ga_secret": true,
  "webhook_url": "https://hooks.example.com/furl",
  "webhook_secret": "whsec_9f2c4e1a7b3d5f60a8c2e4b6d8f0a1c3",
  "expires_at": null,
  "max_clicks": null,
  "clicks_used": 0,
  "created_at": "2026-10-01T09:30:00Z",
  "updated_at": "2026-10-05T12:00:00Z"
}

Errors

StatusCodeWhen
404not_foundNo such link, or it belongs to someone else.
422validation_errorThe link has no webhook configured.

Plus the common errors (including 401 unauthorized).

curl
curl -s -X POST https://furl.io/api/v1/links/launch/webhook/rotate-secret \
  -H "Authorization: Bearer $FURL_API_KEY"

Analytics

All ranges use RFC3339 timestamps. Unique visitors are distinct IP addresses within the range. In breakdowns, an empty value means unknown (or, for referrer_domain, direct traffic).

Webhook delivery log

GET/links/{code}/webhooks

Auth: Session cookie, login token or API key (details)

Recent delivery attempts, newest first. Each retry is a separate item. See Webhooks.

Path parameters

NameTypeDescription
coderequiredstringThe link’s short code (case-sensitive). URL-encode it if it contains special characters.

Query parameters

NameTypeDescription
limitintegerItems to return. Default 50, max 200.

Response

200 OK
{
  "items": [
    {
      "id": 981,
      "response_code": 200,
      "duration_ms": 143,
      "attempt": 1,
      "status": "delivered",
      "created_at": "2026-10-05T12:34:57Z",
      "payload": {
        "id": "dlv_4b7dd7351ffd59362e86a9e5",
        "event": "link.clicked",
        "link": { "id": 42, "short_code": "launch" },
        "click": { "timestamp": "2026-10-05T12:34:56.789Z", "country": "DE", "city": "Berlin" }
      }
    }
  ]
}

Errors

StatusCodeWhen
404not_foundNo such link, or it belongs to someone else.

Plus the common errors (including 401 unauthorized).

curl
curl -s "https://furl.io/api/v1/links/launch/webhooks?limit=10" \
  -H "Authorization: Bearer $FURL_API_KEY"

Account overview

GET/stats/overview

Auth: Session cookie, login token or API key (details)

Totals across all of your links, with a daily click series and your top links.

Query parameters

NameTypeDescription
fromRFC3339Start of the range. Default: 7 days before to.
toRFC3339End of the range. Default: now. The range may not exceed 366 days.

Response

200 OK
{
  "total_links": 12,
  "total_clicks": 4810,
  "series": [
    { "t": "2026-10-04T00:00:00Z", "clicks": 702 },
    { "t": "2026-10-05T00:00:00Z", "clicks": 655 }
  ],
  "top_links": [
    { "short_code": "launch", "clicks": 2210 },
    { "short_code": "aB9xK2q", "clicks": 940 }
  ]
}

Errors

StatusCodeWhen
400bad_requestInvalid from/to (not RFC3339, from after to, or more than 366 days).

Plus the common errors (including 401 unauthorized).

curl
curl -s "https://furl.io/api/v1/stats/overview?from=2026-09-05T00:00:00Z" \
  -H "Authorization: Bearer $FURL_API_KEY"

API keys

API keys belong to a workspace: a key created while a workspace is selected only works with that workspace’s links. Managing keys needs the admin or owner role (403 insufficient_role for members).

List API keys

GET/api-keys

Auth: Session cookie, login token or API key (details)

The workspace’s keys. Only the prefix is returned, never the full key.

Response

200 OK
{
  "items": [
    {
      "id": "b7e1d0c2-4f3a-4e8b-9c6d-1a2b3c4d5e6f",
      "name": "ci-deploy",
      "key_prefix": "furl_k3Jd9",
      "is_active": true,
      "created_at": "2026-10-02T08:00:00Z",
      "last_used_at": "2026-10-05T11:58:12Z"
    }
  ]
}

Errors

StatusCodeWhen
403insufficient_roleYour role in the workspace doesn’t allow this.

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/api-keys \
  -H "Authorization: Bearer $FURL_API_KEY"

Create an API key

POST/api-keys

Auth: Session cookie, login token or API key (details)

The full key is returned in key only in this response. Store it securely.

JSON body

NameTypeDescription
namerequiredstringA label for the key, up to 100 characters.

Response

201 Created
{
  "api_key": {
    "id": "b7e1d0c2-4f3a-4e8b-9c6d-1a2b3c4d5e6f",
    "name": "ci-deploy",
    "key_prefix": "furl_k3Jd9",
    "is_active": true,
    "created_at": "2026-10-02T08:00:00Z",
    "last_used_at": "2026-10-05T11:58:12Z"
  },
  "key": "furl_k3Jd9QwX2mLp7RtY4vBn8ZcF1hGs6KaE0uWo5NiT"
}

Errors

StatusCodeWhen
422validation_errorName longer than 100 characters.
403insufficient_roleYour role in the workspace doesn’t allow this.

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/api-keys \
  -H "Authorization: Bearer $FURL_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"ci-deploy"}'

Revoke an API key

DELETE/api-keys/{id}

Auth: Session cookie, login token or API key (details)

Revokes the key immediately.

Path parameters

NameTypeDescription
idrequiredstringThe key’s id.

Response

204 No Content with an empty body.

Errors

StatusCodeWhen
404not_foundNo such key in this workspace.
403insufficient_roleYour role in the workspace doesn’t allow this.

Plus the common errors (including 401 unauthorized).

curl
curl -s -X DELETE https://furl.io/api/v1/api-keys/b7e1d0c2-4f3a-4e8b-9c6d-1a2b3c4d5e6f \
  -H "Authorization: Bearer $FURL_TOKEN"

Workspaces

Endpoints under /workspace act on the current workspace. Seats (members plus open invitations) and custom domains are limited by the workspace’s plan (max_members, max_domains).

List your workspaces

GET/workspaces

Auth: Session cookie, login token or API key (details)

Every workspace you belong to, personal first, with your role in each.

Response

200 OK
{
  "items": [
    {
      "id": "5c2e8f0a-7d1b-4a3c-9e6f-2b4d6f8a0c1e",
      "name": "Personal",
      "slug": "personal-3f6c2a9e",
      "personal": true,
      "role": "owner",
      "tier": "free",
      "managed": false,
      "custom_code_addon": 0,
      "plan_paid_until": null,
      "created_at": "2026-10-01T09:00:00Z"
    },
    {
      "id": "9a4d7c1e-2b3f-4e5a-8c6d-0f1e2d3c4b5a",
      "name": "Acme Marketing",
      "slug": "acme-marketing",
      "personal": false,
      "role": "owner",
      "tier": "pro",
      "managed": false,
      "custom_code_addon": 0,
      "plan_paid_until": "2027-01-04T10:00:00Z",
      "created_at": "2026-10-02T08:00:00Z"
    }
  ]
}

Errors

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/workspaces \
  -H "Authorization: Bearer $FURL_TOKEN"

Create a team workspace

POST/workspaces

Auth: Session cookie or login token only. API keys are refused (403 session_required).

Creates a team workspace on the Free plan, with you as its owner. You can own up to 10.

JSON body

NameTypeDescription
namerequiredstring1–64 characters.

Response

201 Created
{
  "id": "9a4d7c1e-2b3f-4e5a-8c6d-0f1e2d3c4b5a",
  "name": "Acme Marketing",
  "slug": "acme-marketing",
  "personal": false,
  "role": "owner",
  "tier": "free",
  "managed": false,
  "custom_code_addon": 0,
  "plan_paid_until": null,
  "created_at": "2026-10-02T08:00:00Z"
}

Errors

StatusCodeWhen
422validation_errorMissing or too long name, or you already own 10 team workspaces.

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/workspaces \
  -H "Authorization: Bearer $FURL_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Acme Marketing"}'

Current workspace

GET/workspace

Auth: Session cookie, login token or API key (details)

The current workspace with its plan and usage (as on /me).

Response

200 OK
{
  "workspace": {
    "id": "9a4d7c1e-2b3f-4e5a-8c6d-0f1e2d3c4b5a",
    "name": "Acme Marketing",
    "slug": "acme-marketing",
    "personal": false,
    "role": "owner",
    "tier": "pro",
    "managed": false,
    "custom_code_addon": 0,
    "plan_paid_until": "2027-01-04T10:00:00Z",
    "created_at": "2026-10-02T08:00:00Z"
  },
  "plan": { "code": "pro", "…": "…" },
  "usage": { "active_links": 42, "custom_codes": 3, "custom_code_addon": 0 }
}

Errors

StatusCodeWhen
403not_memberX-Furl-Workspace names a workspace you don’t belong to.

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/workspace \
  -H "Authorization: Bearer $FURL_TOKEN" \
  -H "X-Furl-Workspace: 9a4d7c1e-2b3f-4e5a-8c6d-0f1e2d3c4b5a"

Rename the workspace

PATCH/workspace

Auth: Session cookie, login token or API key (details). Needs the admin or owner role in the current workspace.

JSON body

NameTypeDescription
namerequiredstring1–64 characters.

Response

200 OK
{
  "id": "9a4d7c1e-2b3f-4e5a-8c6d-0f1e2d3c4b5a",
  "name": "Acme Marketing",
  "slug": "acme-marketing",
  "personal": false,
  "role": "owner",
  "tier": "pro",
  "managed": false,
  "custom_code_addon": 0,
  "plan_paid_until": "2027-01-04T10:00:00Z",
  "created_at": "2026-10-02T08:00:00Z"
}

Errors

StatusCodeWhen
403insufficient_roleYour role in the workspace doesn’t allow this.
409personal_workspaceNot possible in a personal workspace.
422validation_errorMissing or too long name.

Plus the common errors (including 401 unauthorized).

curl
curl -s -X PATCH https://furl.io/api/v1/workspace \
  -H "Authorization: Bearer $FURL_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Acme Growth"}'

Delete the workspace

DELETE/workspace

Auth: Session cookie or login token only; the owner of a team workspace.

Permanently deletes the team workspace with all its links (they stop redirecting immediately), their analytics, API keys, invitations and custom domains.

Response

204 No Content with an empty body.

Errors

StatusCodeWhen
403insufficient_roleYour role in the workspace doesn’t allow this.
409personal_workspaceNot possible in a personal workspace.
403session_requiredThe request used an API key.

Plus the common errors (including 401 unauthorized).

curl
curl -s -X DELETE https://furl.io/api/v1/workspace \
  -H "Authorization: Bearer $FURL_TOKEN" \
  -H "X-Furl-Workspace: 9a4d7c1e-2b3f-4e5a-8c6d-0f1e2d3c4b5a"

List members

GET/workspace/members

Auth: Session cookie, login token or API key (details)

Response

200 OK
{
  "items": [
    {
      "user_id": "3f6c2a9e-5b1d-4c47-9a2e-7d0b8f1e4a63",
      "email": "[email protected]",
      "role": "owner",
      "created_at": "2026-10-02T08:00:00Z"
    }
  ]
}

Errors

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/workspace/members \
  -H "Authorization: Bearer $FURL_TOKEN"

Change a member’s role

PATCH/workspace/members/{userID}

Auth: Session cookie, login token or API key (details). Needs the admin or owner role in the current workspace.

Sets the role to member or admin. Only the owner can set owner, which transfers ownership: the previous owner becomes an admin. Returns the updated member list.

Path parameters

NameTypeDescription
userIDrequiredstringThe member’s user_id.

JSON body

NameTypeDescription
rolerequiredstringmember, admin or owner.

Response

200 OK
{
  "items": [
    {
      "user_id": "3f6c2a9e-5b1d-4c47-9a2e-7d0b8f1e4a63",
      "email": "[email protected]",
      "role": "owner",
      "created_at": "2026-10-02T08:00:00Z"
    }
  ]
}

Errors

StatusCodeWhen
403insufficient_roleYour role in the workspace doesn’t allow this.
409owner_requiredThe workspace must keep exactly one owner.
404not_foundNo such member.

Plus the common errors (including 401 unauthorized).

curl
curl -s -X PATCH https://furl.io/api/v1/workspace/members/3f6c2a9e-5b1d-4c47-9a2e-7d0b8f1e4a63 \
  -H "Authorization: Bearer $FURL_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"role":"admin"}'

Remove a member or leave

DELETE/workspace/members/{userID}

Auth: Session cookie, login token or API key (details). Removing someone else needs the admin or owner role; anyone can remove themselves (leave).

The links the member created stay in the workspace. The owner can’t be removed.

Path parameters

NameTypeDescription
userIDrequiredstringThe member’s user_id (yours to leave).

Response

204 No Content with an empty body.

Errors

StatusCodeWhen
403insufficient_roleYour role in the workspace doesn’t allow this.
409owner_requiredThe owner can’t leave or be removed; transfer ownership first.
404not_foundNo such member.

Plus the common errors (including 401 unauthorized).

curl
curl -s -X DELETE https://furl.io/api/v1/workspace/members/3f6c2a9e-5b1d-4c47-9a2e-7d0b8f1e4a63 \
  -H "Authorization: Bearer $FURL_TOKEN"

List open invitations

GET/workspace/invites

Auth: Session cookie, login token or API key (details). Needs the admin or owner role in the current workspace.

Response

200 OK
{
  "items": [
    {
      "id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
      "email": "[email protected]",
      "role": "member",
      "expires_at": "2026-10-13T08:00:00Z",
      "created_at": "2026-10-06T08:00:00Z"
    }
  ]
}

Errors

StatusCodeWhen
403insufficient_roleYour role in the workspace doesn’t allow this.

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/workspace/invites \
  -H "Authorization: Bearer $FURL_TOKEN"

Invite someone

POST/workspace/invites

Auth: Session cookie, login token or API key (details). Needs the admin or owner role in the current workspace.

Emails an invitation link that works for 7 days. The invited person accepts it while signed in with that email address (see accept an invitation). An open invitation uses a seat.

JSON body

NameTypeDescription
emailrequiredstringThe address to invite.
rolerequiredstringmember or admin.

Response

201 Created
{
  "id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
  "email": "[email protected]",
  "role": "member",
  "expires_at": "2026-10-13T08:00:00Z",
  "created_at": "2026-10-06T08:00:00Z"
}

Errors

StatusCodeWhen
403insufficient_roleYour role in the workspace doesn’t allow this.
403member_limitEvery seat on the workspace’s plan is taken (members plus open invitations).
409already_memberThat person is already a member.
409personal_workspaceNot possible in a personal workspace.
422validation_errorInvalid email address or role.
503email_unavailableThe invitation email couldn’t be sent. Try again later.

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/workspace/invites \
  -H "Authorization: Bearer $FURL_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","role":"member"}'

Withdraw an invitation

DELETE/workspace/invites/{id}

Auth: Session cookie, login token or API key (details). Needs the admin or owner role in the current workspace.

Path parameters

NameTypeDescription
idrequiredstringThe invitation’s id.

Response

204 No Content with an empty body.

Errors

StatusCodeWhen
403insufficient_roleYour role in the workspace doesn’t allow this.
404not_foundNo such invitation.

Plus the common errors (including 401 unauthorized).

curl
curl -s -X DELETE https://furl.io/api/v1/workspace/invites/e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b \
  -H "Authorization: Bearer $FURL_TOKEN"

Accept an invitation

POST/invites/accept

Auth: Session cookie or login token only. API keys are refused (403 session_required).

Joins the workspace with the role in the invitation. The signed-in account’s email must be the invited address. Returns the workspace you joined.

JSON body

NameTypeDescription
tokenrequiredstringThe token from the invitation link.

Response

200 OK
{
  "id": "9a4d7c1e-2b3f-4e5a-8c6d-0f1e2d3c4b5a",
  "name": "Acme Marketing",
  "slug": "acme-marketing",
  "personal": false,
  "role": "member",
  "tier": "pro",
  "managed": false,
  "custom_code_addon": 0,
  "plan_paid_until": "2027-01-04T10:00:00Z",
  "created_at": "2026-10-02T08:00:00Z"
}

Errors

StatusCodeWhen
404invite_invalidUnknown, expired, withdrawn or already used invitation.
403invite_email_mismatchThe invitation was sent to a different email address.
403session_requiredThe request used an API key.

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/invites/accept \
  -H "Authorization: Bearer $FURL_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"token":"…"}'

List custom domains

GET/workspace/domains

Auth: Session cookie, login token or API key (details)

status is pending, active (verified), failed or disabled. To verify a domain, create a DNS TXT record named verification_name with the value verification_token. Verified domains will soon be usable for short links.

Response

200 OK
{
  "items": [
    {
      "id": "0d9c8b7a-6f5e-4d3c-2b1a-0f9e8d7c6b5a",
      "hostname": "go.example.com",
      "status": "pending",
      "verification_name": "_furl-verify.go.example.com",
      "verification_token": "furl-verify=3q9TzX1mK8pLw2Rv",
      "verified_at": null,
      "last_checked_at": null,
      "last_error": null,
      "created_at": "2026-10-06T08:00:00Z"
    }
  ]
}

Errors

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/workspace/domains \
  -H "Authorization: Bearer $FURL_TOKEN"

Add a custom domain

POST/workspace/domains

Auth: Session cookie, login token or API key (details). Needs the admin or owner role in the current workspace.

JSON body

NameTypeDescription
hostnamerequiredstringA domain you control, e.g. go.example.com.

Response

201 Created
{
  "id": "0d9c8b7a-6f5e-4d3c-2b1a-0f9e8d7c6b5a",
  "hostname": "go.example.com",
  "status": "pending",
  "verification_name": "_furl-verify.go.example.com",
  "verification_token": "furl-verify=3q9TzX1mK8pLw2Rv",
  "verified_at": null,
  "last_checked_at": null,
  "last_error": null,
  "created_at": "2026-10-06T08:00:00Z"
}

Errors

StatusCodeWhen
403insufficient_roleYour role in the workspace doesn’t allow this.
403domain_limitThe workspace’s plan doesn’t include (more) custom domains.
409domain_takenThe domain is already added to a workspace.
422validation_errorNot a valid hostname, or a furl.io domain.

Plus the common errors (including 401 unauthorized).

curl
curl -s https://furl.io/api/v1/workspace/domains \
  -H "Authorization: Bearer $FURL_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"hostname":"go.example.com"}'

Verify a custom domain

POST/workspace/domains/{id}/verify

Auth: Session cookie, login token or API key (details). Needs the admin or owner role in the current workspace.

Looks up the TXT record now. The domain becomes active when the record is found; otherwise its status is failed with last_error. DNS changes can take a few minutes to show up.

Path parameters

NameTypeDescription
idrequiredstringThe domain’s id.

Response

200 OK
{
  "id": "0d9c8b7a-6f5e-4d3c-2b1a-0f9e8d7c6b5a",
  "hostname": "go.example.com",
  "status": "active",
  "verification_name": "_furl-verify.go.example.com",
  "verification_token": "furl-verify=3q9TzX1mK8pLw2Rv",
  "verified_at": "2026-10-06T08:05:00Z",
  "last_checked_at": "2026-10-06T08:05:00Z",
  "last_error": null,
  "created_at": "2026-10-06T08:00:00Z"
}

Errors

StatusCodeWhen
403insufficient_roleYour role in the workspace doesn’t allow this.
404not_foundNo such domain in this workspace.

Plus the common errors (including 401 unauthorized).

curl
curl -s -X POST https://furl.io/api/v1/workspace/domains/0d9c8b7a-6f5e-4d3c-2b1a-0f9e8d7c6b5a/verify \
  -H "Authorization: Bearer $FURL_TOKEN"

Remove a custom domain

DELETE/workspace/domains/{id}

Auth: Session cookie, login token or API key (details). Needs the admin or owner role in the current workspace.

Path parameters

NameTypeDescription
idrequiredstringThe domain’s id.

Response

204 No Content with an empty body.

Errors

StatusCodeWhen
403insufficient_roleYour role in the workspace doesn’t allow this.
409domain_in_useLinks still use the domain.
404not_foundNo such domain in this workspace.

Plus the common errors (including 401 unauthorized).

curl
curl -s -X DELETE https://furl.io/api/v1/workspace/domains/0d9c8b7a-6f5e-4d3c-2b1a-0f9e8d7c6b5a \
  -H "Authorization: Bearer $FURL_TOKEN"
Webhook payloads and signature verification are documented in Webhooks.