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