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