Skip to main content

API Documentation

The Snipzr API lets you create, manage and measure short links from your own code. It is a plain HTTPS + JSON API: every endpoint below lives under https://api.snipzr.com/v1, takes and returns JSON, and authenticates with a Bearer token.

The API is included in every paid plan (Pro, Team and above). The pricing page is the source of truth for plan limits.

Authentication

Send your token in the Authorization header of every request:

Authorization: Bearer YOUR_TOKEN

There are two kinds of token. Both work on every endpoint; they differ in what they act as.

A workspace token belongs to one workspace and acts as that workspace, at its plan limits. Links it creates belong to the workspace; stats and campaign reports cover the workspace; GET /zones lists the workspace's domains. It is not tied to any person: members can come and go, and the token keeps working. If the workspace is ever transferred, its tokens move with it.

  • Create one in Settings, in the Workspace API tokens card, as an owner or admin. Give it a name (for example ci-deploy or client-acme-report) and select the scopes it needs (below).
  • The secret starts with snz_ws_ and is shown once, at creation. Store it in your secret manager; if you lose it, revoke it and create a new one.
  • Revoking a token stops its requests immediately. Only the last four characters stay visible in the list, so you can tell tokens apart.
  • All tokens of one workspace share that workspace's rate limit (below).

User tokens

A user token acts as you. Create one in Settings, in the User API tokens card. The secret starts with snz_ and is shown once, at creation.

When you create it you pick which of your workspaces it may act in, and what it may do in each one. Access always follows your live membership: what a grant can actually do is your granted scopes capped at your current role there, checked on every request. Leave (or get removed from) a workspace and the token loses it instantly; get re-added and it resumes, capped at your new role. The token card shows each grant's live state.

By default a request acts in your personal workspace. To act in another granted workspace, name it with the X-Workspace-Id header:

Authorization: Bearer snz_...
X-Workspace-Id: WORKSPACE_ID

Requests for a workspace the token has no usable grant for answer 403 with a stable code: NOT_GRANTED (not on the token), GRANT_INACTIVE (your membership there ended), or PLAN_REQUIRED (that workspace is on the Free plan).

Scopes

Both kinds of token can be narrowed to just what they need. The scopes are links:read, links:write, stats:read, usage:read and domains:read. A request outside the token's scopes answers 403 with code: "SCOPE_REQUIRED" and the missing scope. Give automation the smallest set that does the job, and prefer one token per client so you can revoke them independently.

Plan gate

Tokens only work while the workspace (or account) they act in is on a paid plan. On the Free plan requests answer 401 (or 403 with PLAN_REQUIRED for a granted workspace). Existing tokens are kept and start working again after an upgrade.


Conventions

  • Base URL: https://api.snipzr.com/v1. Send Content-Type: application/json on POST and PATCH.
  • Errors are JSON: { "error": "human readable message" }. Some errors add a stable code field (for example CAMPAIGN_REPORT_NOT_IN_PLAN). Messages are in English by default; send Accept-Language: de for German.
  • Timestamps (createdAt, updatedAt) are ISO 8601 in UTC. Analytics windows use UTC calendar dates, YYYY-MM-DD.
  • Additive changes. New fields may appear in responses without a version change, and some fields appear only when they apply (they are marked below). Ignore fields you do not know.
  • Slugs are 3 to 1024 characters of A-Z a-z 0-9 - _, and are unique within a domain. If you use one slug on two of your domains, add ?zoneDomain= to the slug endpoints (GET, PATCH, DELETE and /stats on /urls/:slug) to say which one you mean; without it they act on the first match.

Rate limits

One allowance covers all /v1 endpoints together, per minute:

PlanRequests per minute
Pro300
Team600

Requests made with workspace tokens count against the workspace's allowance, however many tokens it has; a personal token has its own. Above the limit the API answers 429 with a Retry-After header (in seconds). Back off until it passes, then retry. A batch of up to 100 links is one request, so migrations should use POST /urls/batch. GET /usage shows your allowance and quota.


Getting Started

  1. Create a token in Settings (see above).
  2. Check it works with GET /zones. It returns the domains you can create links on.
  3. Create a link with POST /urls, or many at once with POST /urls/batch.
  4. Read the numbers with GET /urls/:slug/stats or, for UTM-tagged links, GET /urls/campaigns. GET /usage tells you how much of the plan's quota is left.

Endpoints

POST /urls

Create a short link.

API Endpoint:

POST https://api.snipzr.com/v1/urls

Request Body

ParameterTypeDescription
urlstringThe destination. Required; http or https; up to 32,767 characters.
titlestringOptional label, up to 256 characters.
slugstringOptional custom slug (3 to 1024 characters of letters, numbers, hyphens, underscores). Omit it to get a generated slug. Reserved words such as login are refused.
zoneDomainstringOptional domain for the link, for example snipzr.com or one of your connected domains. Omit it to use your default domain. Use GET /zones to see the choices.

If the destination carries UTM parameters, Snipzr derives a utm object from it and includes it in every response for that link.

Response 201

{
"url": {
"id": "6a957857666cec1016ee2248",
"slug": "spring-sale",
"url": "https://example.com/sale?utm_source=newsletter&utm_medium=email&utm_campaign=spring",
"title": "Spring sale",
"zoneDomain": "snipzr.com",
"hits": 0,
"isEnabled": true,
"createdAt": "2026-08-31T12:49:27.444Z",
"updatedAt": "2026-08-31T12:49:27.444Z",
"utm": { "source": "newsletter", "medium": "email", "campaign": "spring" }
}
}

Errors: 400 invalid URL or slug, 403 link quota of the plan used up (INSUFFICIENT_QUOTA), 409 slug already exists on that domain.

cURL Command

curl -X POST https://api.snipzr.com/v1/urls \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/sale",
"title": "Spring sale",
"slug": "spring-sale",
"zoneDomain": "snipzr.com"
}'

POST /urls/batch

Create up to 100 links in one request. Built for migrations and imports: a batch counts as a single request against your rate limit, and every item is processed on its own, so one bad row never fails the rest.

API Endpoint:

POST https://api.snipzr.com/v1/urls/batch

Request Body

{
"links": [
{ "url": "https://example.com/report", "title": "Spring report", "slug": "spring-report", "zoneDomain": "snipzr.com" },
{ "url": "https://example.com/plain" }
]
}

Each item takes the same fields as POST /urls. Between 1 and 100 items; the request body may be at most 1 MB.

Response 200

A processed batch always answers 200. Read results to learn what happened to each item: it is in the same order as links, and every entry carries either the created link or the status, code and message the single-link endpoint would have given.

{
"created": 2,
"failed": 2,
"results": [
{ "index": 0, "status": 201, "url": { "id": "6a957b5c7c364cb08068e031", "slug": "spring-report", "url": "https://example.com/report", "title": "Spring report", "zoneDomain": "snipzr.com", "hits": 0, "isEnabled": true, "createdAt": "2026-08-31T13:20:11.102Z", "updatedAt": "2026-08-31T13:20:11.102Z" } },
{ "index": 1, "status": 201, "url": { "id": "6a957b5c7c364cb08068e032", "slug": "k3x9q", "url": "https://example.com/plain", "title": "", "zoneDomain": "snipzr.com", "hits": 0, "isEnabled": true, "createdAt": "2026-08-31T13:20:11.140Z", "updatedAt": "2026-08-31T13:20:11.140Z" } },
{ "index": 2, "status": 400, "code": "VALIDATION_FAILED", "error": "URL must be a valid HTTP or HTTPS URL" },
{ "index": 3, "status": 409, "code": "SLUG_EXISTS", "error": "Slug already exists" }
]
}

Item codes you will see: VALIDATION_FAILED (400), SLUG_EXISTS (409), SLUG_RESERVED and SLUG_INVALID (400), INSUFFICIENT_QUOTA (403). Once the plan's link quota is used up, every remaining item fails with INSUFFICIENT_QUOTA without being attempted, so check GET /usage before a large import.

Envelope errors are 400 with a plain error: no items, more than 100 items, or a body that is not JSON.

cURL Command

curl -X POST https://api.snipzr.com/v1/urls/batch \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"links": [
{ "url": "https://example.com/report", "title": "Spring report", "slug": "spring-report" },
{ "url": "https://example.com/plain" }
]
}'

PATCH /urls/:slug

Update a short link. Send only the fields you want to change.

API Endpoint:

PATCH https://api.snipzr.com/v1/urls/:slug

Request Body

ParameterTypeDescription
urlstringNew destination (http or https, up to 32,767 characters). The utm object is re-derived from it.
titlestringNew title, up to 256 characters.
newSlugstringNew slug (same rules as on create).
isEnabledbooleanfalse pauses the link (it stops redirecting), true resumes it.

Optional query parameter zoneDomain selects the domain when the slug exists on more than one. Edits are never metered: change the destination as often as you need, on every plan.

Response 200

Returns { "url": { ... } } with the updated URL object.

Errors: 400 invalid value or slug, 404 no such link in this workspace, 409 newSlug already exists on that domain.

cURL Command

curl -X PATCH https://api.snipzr.com/v1/urls/spring-sale \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/sale-2026",
"title": "Spring sale 2026",
"isEnabled": true
}'

GET /urls

List the links of the workspace, newest first, with pagination.

API Endpoint:

GET https://api.snipzr.com/v1/urls?page=1&limit=10

Query Parameters

ParameterTypeDescription
pagenumberPage number, starting at 1 (default: 1).
limitnumberLinks per page (default: 10, maximum: 100). A larger value is a 400.
zoneDomainstringOnly links on this domain.
qstringOnly links whose slug, title or destination contains this text (case-insensitive, literal; up to 200 characters). Handy for finding an existing link by destination before creating a duplicate.
campaignstringOnly links whose derived utm.campaign equals this value.

Filters combine, and total reflects the filtered set.

Response 200

{
"urls": [
{
"id": "6a957857666cec1016ee2248",
"slug": "spring-sale",
"url": "https://example.com/sale",
"title": "Spring sale",
"zoneDomain": "snipzr.com",
"hits": 1284,
"isEnabled": true,
"createdAt": "2026-08-31T12:49:27.444Z",
"updatedAt": "2026-08-31T12:49:27.444Z"
}
],
"total": 42
}

total is the number of links in the workspace, so you can compute the page count. On Team workspaces with UTM conventions switched on, a link whose destination breaks those conventions carries "utmDrift": true; the field is absent otherwise.

cURL Command

curl "https://api.snipzr.com/v1/urls?page=1&limit=50" \
-H "Authorization: Bearer YOUR_TOKEN"

GET /urls/:slug

Get one link by its slug.

API Endpoint:

GET https://api.snipzr.com/v1/urls/:slug

Response 200

{
"url": {
"id": "6a957857666cec1016ee2248",
"slug": "spring-sale",
"url": "https://example.com/sale?utm_source=newsletter&utm_medium=email&utm_campaign=spring",
"title": "Spring sale",
"zoneDomain": "snipzr.com",
"hits": 1284,
"isEnabled": true,
"createdAt": "2026-08-31T12:49:27.444Z",
"updatedAt": "2026-08-31T12:49:27.444Z",
"utm": { "source": "newsletter", "medium": "email", "campaign": "spring" }
}
}

Optional query parameter zoneDomain selects the domain when the slug exists on more than one. Errors: 400 malformed slug, 404 no such link in this workspace (or not on that domain).

cURL Command

curl "https://api.snipzr.com/v1/urls/spring-sale?zoneDomain=snipzr.com" \
-H "Authorization: Bearer YOUR_TOKEN"

GET /urls/:slug/stats

Pre-aggregated analytics for one link: clicks per day, human vs. bots, countries, referrers, devices, and the QR vs. link split. Cookie-less and aggregate only (no per-click rows, no visitor identifiers). See Link stats API for the full response schema, window and retention semantics, and freshness.

API Endpoint:

GET https://api.snipzr.com/v1/urls/:slug/stats?from=YYYY-MM-DD&to=YYYY-MM-DD

from and to are optional UTC dates (default: the last 30 days). A window longer than your plan's analytics retention is clamped, not rejected; the effective start is echoed in range.clampedTo. from after to is a 400; an unknown slug is a 404. zoneDomain selects the domain when the slug exists on more than one.

cURL Command

curl "https://api.snipzr.com/v1/urls/spring-sale/stats?from=2026-08-01&to=2026-08-31" \
-H "Authorization: Bearer YOUR_TOKEN"

GET /urls/campaigns

The campaign report of the workspace: every campaign found in your links' UTM tags, with link counts, human clicks, QR scans, a daily series, and a source and medium breakdown. This is the endpoint behind the in-app Campaigns page, exposed for automated reporting (for example an agency's per-client script).

Available on Pro and above. Workspace tokens report on their workspace; a personal token reports on your personal links.

API Endpoint:

GET https://api.snipzr.com/v1/urls/campaigns?from=YYYY-MM-DD&to=YYYY-MM-DD

from and to are optional UTC dates (default: the last 30 days). Like link stats, a window longer than your plan's analytics retention is clamped, not rejected.

Response 200

{
"report": {
"range": { "from": "2026-08-01", "to": "2026-08-31", "clampedTo": "2026-08-01", "retentionDays": 730 },
"teaser": false,
"totalCampaigns": 5,
"taggedLinks": 6,
"campaigns": [
{
"campaign": "spring-launch",
"linkCount": 2,
"humanClicks": 1582,
"qrScans": 201,
"series": [{ "date": "2026-08-02", "human": 41 }],
"drill": [{ "source": "newsletter", "medium": "email", "linkCount": 2, "humanClicks": 1582 }]
}
],
"freshness": "todayLive"
}
}

campaigns is omitted when there are no UTM-tagged links in the window. A plan without campaign reports receives 403 with "code": "CAMPAIGN_REPORT_NOT_IN_PLAN".

cURL Command

curl "https://api.snipzr.com/v1/urls/campaigns?from=2026-08-01&to=2026-08-31" \
-H "Authorization: Bearer YOUR_TOKEN"

DELETE /urls/:slug

Delete a link permanently. The short URL stops resolving right away and its analytics are removed.

API Endpoint:

DELETE https://api.snipzr.com/v1/urls/:slug

Response 200

{
"message": "URL deleted successfully"
}

Optional query parameter zoneDomain deletes only the link on that domain. Errors: 400 malformed slug, 404 no such link in this workspace, 403 the link was restricted by Snipzr and cannot be deleted by its owner.

cURL Command

curl -X DELETE https://api.snipzr.com/v1/urls/spring-sale \
-H "Authorization: Bearer YOUR_TOKEN"

GET /usage

The workspace's plan, how much of this period's link quota is used, the tracked-events meter, and the API and analytics entitlements. Read it before an import to size your batches, or to show quota in your own tooling.

API Endpoint:

GET https://api.snipzr.com/v1/usage

Response 200

{
"usage": {
"plan": "team",
"links": { "used": 412, "limit": 5000, "remaining": 4588, "periodStart": "2026-08-15T09:12:00Z", "periodEnd": "2026-09-15T09:12:00Z" },
"events": { "used": 18230, "limit": 100000, "periodStart": "2026-08-15T09:12:00Z", "periodEnd": "2026-09-15T09:12:00Z", "locked": false },
"api": { "requestsPerMinute": 600, "batchMaxLinks": 100 },
"analytics": { "retentionDays": 730, "campaignReports": true }
}
}

links is the new-links allowance of the current billing period (yearly plans carry the whole year's pool). events is the tracked-human-events meter; locked is true while the meter is over its allowance. events is omitted if the meter is momentarily unavailable; everything else is always present.

cURL Command

curl https://api.snipzr.com/v1/usage \
-H "Authorization: Bearer YOUR_TOKEN"

GET /zones

The domains you can create links on: the shared Snipzr domains (global) and the custom domains connected to the workspace (user). Use a domain value as zoneDomain when creating a link.

API Endpoint:

GET https://api.snipzr.com/v1/zones

Response 200

{
"global": [
{
"domain": "snipzr.com",
"name": "Snipzr",
"zoneType": "global",
"isEnabled": true,
"isDefault": true
}
],
"user": [
{
"domain": "go.example.com",
"name": "Example short links",
"zoneType": "user",
"isEnabled": true,
"isDefault": false,
"tlsState": "provisioned",
"ownershipState": "verified",
"rootRedirectURL": "https://example.com"
}
]
}

Only enabled shared domains are listed; your own domains are listed even while disabled, so you can see their state. Links can be created on a custom domain once ownershipState is verified and the domain is enabled.

cURL Command

curl https://api.snipzr.com/v1/zones \
-H "Authorization: Bearer YOUR_TOKEN"

QR codes

There is no QR endpoint because you do not need one. Every short link has a scan URL:

https://<zoneDomain>/q/<slug>

Encode that URL with any QR library (or the download in the app, which uses the same URL). It redirects exactly like https://<zoneDomain>/<slug>, and because of the /q/ path Snipzr counts the visit as a scan rather than a click: it shows up as source.qr in GET /urls/:slug/stats and as qrScans in GET /urls/campaigns. Print the /q/ form; share the plain form.


Response Reference

HTTP Status Codes

StatusMeaning
200Success (also a processed batch; read results for per-item outcomes)
201Created (POST /urls)
400Bad request: validation failed (invalid URL, slug, date window or page size). The error field says which.
401Missing or invalid token, or the workspace is on the Free plan
403The token lacks a scope (SCOPE_REQUIRED) or a usable grant for the selected workspace (NOT_GRANTED, GRANT_INACTIVE, PLAN_REQUIRED)
403Not included in the plan (link quota used up, campaign reports), or the link is restricted
404No such link in this workspace
409The slug already exists on that domain
423The workspace is finalizing an ownership transfer; retry after the Retry-After seconds (writes only)
429Rate limit exceeded; retry after the Retry-After seconds
500Something failed on our side. Retry with backoff; we are alerted automatically
503Analytics store unavailable and no live data covers the window (stats only)

URL Object

FieldTypeDescription
idstringUnique identifier of the link
slugstringThe short URL path (for example spring-sale)
urlstringThe destination the short link redirects to
titlestringOptional label; empty string when not set
zoneDomainstringThe domain of the short link (for example snipzr.com)
hitsnumberLifetime redirects of the link, including bots. Use GET /urls/:slug/stats for human clicks
isEnabledbooleantrue while the link redirects, false while paused
createdAtstringISO 8601 timestamp of creation (UTC)
updatedAtstringISO 8601 timestamp of the last change (UTC)
utmobjectUTM parameters derived from the destination (source, medium, campaign, term, content, each only when present). Only when the destination carries UTM parameters
utmDriftbooleantrue when the destination breaks the workspace's UTM conventions (Team). Only in GET /urls, only when it applies
isRestrictedbooleantrue when Snipzr has restricted the link under its abuse policy. Only when true
isUnsafebooleantrue when the destination is currently flagged as unsafe (phishing, malware). Only when true

Zone Object

FieldTypeDescription
domainstringThe domain name (for example snipzr.com)
namestringDisplay name of the domain
zoneTypestringglobal (shared Snipzr domain) or user (your custom domain)
isEnabledbooleanWhether the domain currently serves links
isDefaultbooleanWhether it is the default for new links when zoneDomain is omitted
tlsStatestringCertificate status, for example provisioned (custom domains only)
ownershipStatestringDomain verification status, for example verified (custom domains only)
rootRedirectURLstringWhere the bare domain redirects; empty string when not set (custom domains only)

Support

Need help with the API?