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.
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 |