Skip to content

API reference

Build on 2xurl.

A JSON REST API for everything in the dashboard — create links, design QR codes, verify domains and read analytics from your own code.

Last updated: Version: v1

1. Introduction

All endpoints live under one base URL. Requests and responses are JSON; send Content-Type: application/json with a body.

Base URL
https://2xurl.com/api/v1
  • Bearer keys

    Workspace API keys with the scopes you choose.

  • Fair limits

    Per-minute and monthly limits from your plan, sent back as headers.

  • Signed webhooks

    HMAC-SHA256 signatures on every delivery.

2. Authentication

  • Create an API key in your workspace settings. It is shown once — store it somewhere safe.
  • Keys start with 2x_ and belong to one workspace, so you do not need to send a workspace id.
  • A key acts with the role of the member who created it. If that member is removed, the key stops working.
Every request
curl https://2xurl.com/api/v1/links \
  -H "Authorization: Bearer 2x_your_key_here"

Never put an API key in browser or mobile app code. Call the API from your server.

3. Scopes

Give each key only the scopes it needs. A request outside them is refused with 403.

  • links.read

    List and read links, folders and tags

  • links.write

    Create, update, archive and delete links

  • qr.read

    Read and download QR codes

  • qr.write

    Design QR codes and upload logos

  • domains.read

    List and read custom domains

  • domains.write

    Add, verify and remove domains

  • campaigns.read

    Read campaigns

  • campaigns.write

    Create and change campaigns

  • bio.read

    Read bio pages

  • bio.write

    Create and change bio pages

  • analytics.read

    Read click analytics

  • workspaces.read

    Read workspace details

  • workspaces.write

    Change workspace settings

  • billing.read

    Read wallet and usage

4. Rate Limits

  • Each key has a per-minute limit and each workspace a monthly request quota, both set by your plan.
  • Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds), plus X-Quota-Limit and X-Quota-Remaining.
  • Over a limit you get 429 rate_limited with a Retry-After header. Wait that many seconds before retrying.
  • Send an Idempotency-Key header on POST, PATCH and PUT requests so a retried request is not applied twice.

5. Errors

Errors use standard HTTP status codes and one JSON shape:

{
  "error": { "code": "slug_taken", "message": "That back-half is already in use", "details": null },
  "requestId": "a9116b45-2fbb-4e09-a359-5f3874e7dfe8"
}
400validation_failedA field is missing or invalid; see details
400invalid_destinationThe destination URL is malformed or not allowed
400unsafe_destinationThe destination is flagged by Google Safe Browsing
400domain_not_verifiedThe domain is not verified yet
401unauthorizedMissing, invalid or expired API key
402insufficient_balanceWallet is empty for a billable action
403forbiddenKey lacks the scope or role for this action
404not_foundThe resource does not exist in this workspace
409slug_takenThat back-half is already used on the domain
429rate_limitedToo many requests; wait for Retry-After seconds
500internal_errorSomething went wrong on our side

Include the requestId when you contact support about a failed request.

6. Pagination

Lists return up to 25 items by default and at most 100 (limit). Cursor-paginated lists answer with data, hasMore and nextCursor; pass cursor=nextCursor to get the next page.

{ "data": [ ... ], "hasMore": true, "nextCursor": "clx8k2..." }

8. QR Codes

GET/links/{linkId}/qrGet a link’s QR design
POST/links/{linkId}/qr/previewPreview a design without saving
PUT/links/{linkId}/qrSave a QR design
GET/links/{linkId}/qr/downloadDownload the QR code
POST/qr/bulkGenerate QR codes for many links
POST/qr/logoUpload a logo for QR codes

9. Domains

Coming soon. Custom domains are paused while we finish and test them; these endpoints are listed for reference and may refuse requests until then.

GET/domainsList domains
POST/domainsAdd a domain
GET/domains/{domainId}Get one domain and its DNS records
PATCH/domains/{domainId}Update a domain
POST/domains/{domainId}/verifyCheck DNS and verify the domain
POST/domains/{domainId}/defaultMake it the workspace default
DELETE/domains/{domainId}Remove a domain

A domain must be verified before links can use it; otherwise you get domain_not_verified.

10. Analytics

GET/analytics/overviewClicks and unique clicks over time
GET/analytics/breakdownClicks grouped by a dimension
GET/analytics/top-linksMost-clicked links
GET/analytics/top-campaignsMost-clicked campaigns
GET/analytics/eventsRecent click events (cursor pagination)
GET/analytics/exportExport analytics as CSV

Common query parameters: from, to (ISO dates), tz (IANA time zone such as Asia/Kolkata), linkId, domainId. breakdown takes a dimension: country, device, browser, os, referrer, utm_source, utm_medium, utm_campaign or source.

Clicks by country for one link
curl "https://2xurl.com/api/v1/analytics/breakdown?dimension=country&linkId=LINK_ID&from=2026-10-01&to=2026-10-16" \
  -H "Authorization: Bearer 2x_your_key_here"

11. Webhooks

GET/webhooks/eventsList the events you can subscribe to
GET/webhooksList webhooks
POST/webhooksCreate a webhook
PATCH/webhooks/{webhookId}Update a webhook
DELETE/webhooks/{webhookId}Delete a webhook
GET/webhooks/{webhookId}/deliveriesList recent deliveries
POST/webhooks/{webhookId}/deliveries/{deliveryId}/retryRetry a delivery

Events: link.created, link.updated, link.deleted, domain.verified, workspace.member_invited, workspace.member_joined.

  • Each delivery is a JSON POST with headers X-2xurl-Event, X-2xurl-Delivery and X-2xurl-Signature.
  • The signature is sha256= followed by the HMAC-SHA256 of the raw body, keyed with your webhook secret.
  • Answer with a 2xx status. Failed deliveries are retried up to 6 times with exponential backoff; a webhook that keeps failing is disabled automatically.

See the webhooks guide for a verification example.

12. Support

Questions about the API? Email [email protected] with the requestId of the request, or check the Status page if something looks down.