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.
Workspace tokens (recommended for automation)
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-deployorclient-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. SendContent-Type: application/jsononPOSTandPATCH. - Errors are JSON:
{ "error": "human readable message" }. Some errors add a stablecodefield (for exampleCAMPAIGN_REPORT_NOT_IN_PLAN). Messages are in English by default; sendAccept-Language: defor 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,DELETEand/statson/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:
| Plan | Requests per minute |
|---|---|
| Pro | 300 |
| Team | 600 |
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
- Create a token in Settings (see above).
- Check it works with
GET /zones. It returns the domains you can create links on. - Create a link with
POST /urls, or many at once withPOST /urls/batch. - Read the numbers with
GET /urls/:slug/statsor, for UTM-tagged links,GET /urls/campaigns.GET /usagetells 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
| Parameter | Type | Description |
|---|---|---|
url | string | The destination. Required; http or https; up to 32,767 characters. |
title | string | Optional label, up to 256 characters. |
slug | string | Optional 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. |
zoneDomain | string | Optional 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
| Parameter | Type | Description |
|---|---|---|
url | string | New destination (http or https, up to 32,767 characters). The utm object is re-derived from it. |
title | string | New title, up to 256 characters. |
newSlug | string | New slug (same rules as on create). |
isEnabled | boolean | false 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
| Parameter | Type | Description |
|---|---|---|
page | number | Page number, starting at 1 (default: 1). |
limit | number | Links per page (default: 10, maximum: 100). A larger value is a 400. |
zoneDomain | string | Only links on this domain. |
q | string | Only 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. |
campaign | string | Only 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
| Status | Meaning |
|---|---|
200 | Success (also a processed batch; read results for per-item outcomes) |
201 | Created (POST /urls) |
400 | Bad request: validation failed (invalid URL, slug, date window or page size). The error field says which. |
401 | Missing or invalid token, or the workspace is on the Free plan |
403 | The token lacks a scope (SCOPE_REQUIRED) or a usable grant for the selected workspace (NOT_GRANTED, GRANT_INACTIVE, PLAN_REQUIRED) |
403 | Not included in the plan (link quota used up, campaign reports), or the link is restricted |
404 | No such link in this workspace |
409 | The slug already exists on that domain |
423 | The workspace is finalizing an ownership transfer; retry after the Retry-After seconds (writes only) |
429 | Rate limit exceeded; retry after the Retry-After seconds |
500 | Something failed on our side. Retry with backoff; we are alerted automatically |
503 | Analytics store unavailable and no live data covers the window (stats only) |
URL Object
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier of the link |
slug | string | The short URL path (for example spring-sale) |
url | string | The destination the short link redirects to |
title | string | Optional label; empty string when not set |
zoneDomain | string | The domain of the short link (for example snipzr.com) |
hits | number | Lifetime redirects of the link, including bots. Use GET /urls/:slug/stats for human clicks |
isEnabled | boolean | true while the link redirects, false while paused |
createdAt | string | ISO 8601 timestamp of creation (UTC) |
updatedAt | string | ISO 8601 timestamp of the last change (UTC) |
utm | object | UTM parameters derived from the destination (source, medium, campaign, term, content, each only when present). Only when the destination carries UTM parameters |
utmDrift | boolean | true when the destination breaks the workspace's UTM conventions (Team). Only in GET /urls, only when it applies |
isRestricted | boolean | true when Snipzr has restricted the link under its abuse policy. Only when true |
isUnsafe | boolean | true when the destination is currently flagged as unsafe (phishing, malware). Only when true |
Zone Object
| Field | Type | Description |
|---|---|---|
domain | string | The domain name (for example snipzr.com) |
name | string | Display name of the domain |
zoneType | string | global (shared Snipzr domain) or user (your custom domain) |
isEnabled | boolean | Whether the domain currently serves links |
isDefault | boolean | Whether it is the default for new links when zoneDomain is omitted |
tlsState | string | Certificate status, for example provisioned (custom domains only) |
ownershipState | string | Domain verification status, for example verified (custom domains only) |
rootRedirectURL | string | Where the bare domain redirects; empty string when not set (custom domains only) |
Support
Need help with the API?