REST API quickstart
Everything the slim.to dashboard does is backed by a JSON API at
https://slim.to. The full OpenAPI spec is served at
https://slim.to/docs/api (Swagger UI); the raw
JSON is at https://slim.to/openapi/openapi.json.
Auth
Create a personal API key and send it as X-API-Key:
export SLIMTO_API_KEY=slim_your_key_here
Shorten a URL
curl -s -X POST https://slim.to/api/v1/links \
-H "X-API-Key: $SLIMTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"target_url": "https://example.com/pitch", "title": "Pitch deck"}'
Response (trimmed):
{
"id": "52cab9d5-…",
"slug": "x0b31u",
"short_url": "https://slim.to/x0b31u",
"type": "redirect",
"is_active": true
}
Optional body fields:
| Field | |
|---|---|
slug |
Custom slug; 409 if taken |
domain |
A verified custom domain to serve the link from (Pro) |
password |
Viewers must enter it before the redirect |
allowed_domains |
List of email domains; viewers verify an address on one of them |
blocked_emails |
List of email addresses to refuse |
expires_at |
ISO 8601 datetime after which the link stops working |
max_views |
Disable the link after this many views |
tags |
List of strings for grouping; filter on them with list |
team_id |
Create the link in a team workspace (Team plan) |
Many at once: POST /api/v1/links/bulk with
{"links": [{"target_url": "…", "title": "…", "slug": "…"}, …], "domain": "…"}
creates up to 1000 redirect links in one request and returns
{"created": n, "failed": n, "results": [...]} with a per-item outcome.
Upload a file → hosted link
Multipart upload; returns the file record and the hosted link in one call:
curl -s -X POST https://slim.to/api/v1/files \
-H "X-API-Key: $SLIMTO_API_KEY" \
-F "file=@report.pdf" \
-F "title=Q3 Report"
Optional form fields: slug, password, domain, tags (a JSON array or a
comma-separated list).
Allowed types: pdf, png, jpg, jpeg, gif, html, zip, mp4, and
Office documents — docx, pptx, xlsx, doc, ppt, xls. Max 100 MB.
Office files are download-and-track only: page-level read analytics needs a renderer, which today means a PDF or a hosted page.
Files over 30 MB must use the two-step flow (the single-request path is capped by the hosting platform):
# 1. Get a signed upload URL
curl -s -X POST https://slim.to/api/v1/files/upload-url \
-H "X-API-Key: $SLIMTO_API_KEY" -H "Content-Type: application/json" \
-d '{"filename": "demo.mp4"}'
# → {"storage_key": "...", "upload_url": "...", "token": "...", "content_type": "video/mp4"}
# 2. PUT the file to upload_url
curl -s -X PUT "<upload_url>" -H "Content-Type: video/mp4" --data-binary @demo.mp4
# 3. Confirm — creates the File + Link records
curl -s -X POST https://slim.to/api/v1/files/confirm \
-H "X-API-Key: $SLIMTO_API_KEY" -H "Content-Type: application/json" \
-d '{"storage_key": "<storage_key>", "filename": "demo.mp4", "title": "Demo video"}'
confirm takes the same optional fields as the direct upload (slug,
password, domain, tags). Ignore token unless the upload_url you
were given says otherwise; it is there for storage backends that need it.
Replace the file behind a link
Publish a new version without changing the URL or losing its analytics:
curl -s -X POST https://slim.to/api/v1/links/<link_id>/replace-file \
-H "X-API-Key: $SLIMTO_API_KEY" \
-F "file=@report-v2.pdf"
The previous file is overwritten and is not recoverable.
DELETE /api/v1/files/<file_id> removes an uploaded file outright.
List your links
curl -s "https://slim.to/api/v1/links?q=report&tag=q3&status=active&page=1&per_page=20" \
-H "X-API-Key: $SLIMTO_API_KEY"
Returns {"links": [...], "total": n, "page": 1, "per_page": 20, "pages": n}.
q searches slug, title, and target URL. Narrow the list further with tag
(exact tag, case-insensitive), domain, status (active or disabled) and
team_id; filters apply before pagination, so total and pages reflect them.
For autocomplete, GET /api/v1/links/suggest?q=rep returns up to eight matching
links plus the tags, domains and teams you can filter on. With an empty q
it returns the full facet lists and your most recent links.
Analytics for a link
curl -s https://slim.to/api/v1/analytics/links/<link_id> \
-H "X-API-Key: $SLIMTO_API_KEY"
Returns totals (total_views, unique_viewers, avg_time_seconds,
median_time_seconds, bounce_rate, total_downloads, last_viewed_at),
breakdowns (device_breakdown, browser_breakdown, os_breakdown,
country_breakdown, region_breakdown, city_breakdown, referrer_breakdown,
per-page page_breakdown plus completion_rate for PDFs), a 30-day
daily_views series, a viewers roster grouped by email or device signature,
and per-session detail in sessions (most recent 200; sessions_total has the
full count). Add /export to the path for CSV (Pro). Visits by the link's
owner and active teammates are recorded but hidden from every analytics
response by default; add include_own=1 to count them, labelled you or team.
referrer_breakdown counts each session once under where it came from: a
known site by name (X for t.co, LinkedIn, Reddit), any other site by
hostname, and Direct when the browser sent no referrer. Many apps (LinkedIn,
Reddit, Slack, email) send none, so tag the link where you post it:
https://slim.to/abc123?ref=linkedin (or ?utm_source=linkedin). A tag
overrides the referrer and is exported as source in the CSV.
More analytics endpoints:
| Method | Path | |
|---|---|---|
GET |
/api/v1/analytics/overview?days=30&domain=&tag= |
Totals with a previous-period comparison, top links, identified viewers, and daily and hourly series across all your links (days up to 90) |
GET |
/api/v1/analytics/overview/export?days=30&domain=&tag= |
One CSV row per link for the period (Pro) |
GET |
/api/v1/analytics/activity?types=open,download,link_created&limit=20 |
Your most recent events across all links (limit up to 50) |
GET |
/api/v1/analytics/links/<id>/split |
Per-variant numbers for a link running an A/B split test |
GET |
/api/v1/analytics/links/<id>/conversions |
Funnel: visitors → engaged → converted, per variant |
To get pushed instead of polling, set up a webhook.
Custom domains
# Add — returns the DNS record to create, status "pending"
curl -s -X POST https://slim.to/api/v1/domains \
-H "X-API-Key: $SLIMTO_API_KEY" -H "Content-Type: application/json" \
-d '{"custom_domain": "share.example.com"}'
# Verify once DNS is in place
curl -s -X POST https://slim.to/api/v1/domains/<domain_id>/verify \
-H "X-API-Key: $SLIMTO_API_KEY"
GET /api/v1/domains lists yours with status (pending or verified);
DELETE /api/v1/domains/<domain_id> removes one. Pass a verified domain as
domain when creating links. Custom domains need a Pro or Team plan.
Other useful endpoints
| Method | Path | |
|---|---|---|
GET |
/api/v1/links/<id> |
Full link detail, including file_info for hosted files |
PATCH |
/api/v1/links/<id> |
Update anything: title, target_url, password, expires_at, max_views, tags, is_active, notify_on_open, OG overrides (og_title, og_description, og_image_url), routing rules, split_test, viewer branding (brand_logo_url, brand_color, hide_slim_badge), QR styling |
DELETE |
/api/v1/links/<id> |
Delete a link |
GET |
/api/v1/links/<id>/qr |
QR code PNG for the link |
GET |
/api/v1/webhooks |
Outbound webhooks |
GET |
/api/v1/links/<id>/submissions |
Form submissions on a hosted page (/export for CSV) |
GET |
/api/v1/teams |
Team workspaces you belong to (Team plan) |
Hosted pages (/api/v1/pages, /api/v1/links/<id>/page) are in beta and
documented only in the OpenAPI spec for now.
Errors
Errors are JSON: {"error": "message"} with a conventional status code:
| Status | When |
|---|---|
400 |
Bad input — missing field, file type not allowed, file over 100 MB |
401 |
Bad or revoked key |
403 |
Plan limit. Either the active-link cap ("Plan limit reached") or a gated feature, in which case the body also has "feature", "current_plan" and "upgrade_required": true |
404 |
Link, file or domain not found (or not yours) |
409 |
Slug already taken on that domain |
429 |
More than 120 requests in a minute on this key |