Documentation
Share a file
POST
/v1/dropsDropsSend the file as the request body and get a short link to it. snip.link/<code> shows the file with a download button and its QR code, snip.link/<code>.<its extension> is the file itself, and any other ending converts it (.pdf, .png, .md ...). Send Content-Length; name the file with ?name= or an X-File-Name header. Free shares files up to 100 MB and keeps 5 GB of them; Pro shares files up to 5 GB and keeps 100 GB. Programs and installers are never accepted. The same works without an API: curl -T photo.jpg https://snip.link. Also available as the MCP tool drop_file.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | optional | The file's name. |
ending | string | optional | A custom ending for the link. |
title | string | optional | A title for your dashboard; the file's name when left out. |
expires_at | string | optional | When the link stops working (ISO 8601). Files are kept until then, or for good when left out. |
Example request
curl -s -X POST "https://snip.link/v1/drops?name=spring-menu.pdf&ending=menu" \ -H "Authorization: Bearer $SNIP_KEY"
Example response
{
"link": {
"id": "lnk_7f0c2a2b4bb04a8b",
"code": "spring-menu",
"domain": "snip.link",
"short_url": "https://snip.link/spring-menu",
"url": "https://example.com/menus/spring-2026",
"title": "Spring menu",
"redirect": 302,
"disabled": false,
"held": false,
"expires_at": null,
"expired_url": null,
"expired": false,
"starts_at": null,
"scheduled": false,
"max_clicks": null,
"limit_reached": false,
"track": true,
"password_protected": false,
"routes": [
{
"when": "all",
"conditions": [
{
"kind": "os",
"values": [
"ios"
]
}
],
"url": "https://apps.apple.com/app/id1234567890",
"match": "ios"
},
{
"name": "Lunch menu",
"when": "all",
"conditions": [
{
"kind": "time",
"from": "11:00",
"to": "15:00",
"tz": "America/New_York"
},
{
"kind": "weekday",
"values": [
1,
2,
3,
4,
5
],
"tz": "America/New_York"
}
],
"url": "https://example.com/menus/lunch"
}
],
"forward_query": false,
"preview": {
"title": "Spring menu",
"description": "Twelve new dishes, out now.",
"image": "screenshot"
},
"health": {
"status": "ok",
"code": 200,
"checked_at": "2026-10-20T08:00:00.000Z"
},
"clicks": 1284,
"qr_style": {
"style": "rounded",
"fg": "#111111",
"bg": "#ffffff",
"ecc": "M"
},
"qr_png_url": "https://snip.link/qr.png?data=https%3A%2F%2Fsnip.link%2Fspring-menu%3Fqr%3D1",
"qr_svg_url": "https://snip.link/qr.svg?data=https%3A%2F%2Fsnip.link%2Fspring-menu%3Fqr%3D1",
"snapshots": "public",
"watch": null,
"file": null,
"stats_url": null,
"created_at": "2026-10-01T09:12:40.000Z",
"updated_at": "2026-10-18T16:03:11.000Z"
}
}Response fields
| Field | Type | Description |
|---|---|---|
link | Link | |
link.id | string | Stable id. Every link route accepts it, or a current or previous ending. |
link.code | string | The current ending: the path after the domain. |
link.domain | string | The hostname the link lives on: snip.link, or one of the account's own domains. |
link.short_url | string | The short link, ready to share or print. |
link.url | string | Where the link goes now. Change it any time; printed codes follow. |
link.title | string, null | A name for the dashboard. Null when unset. |
link.redirect | integer | 302 (default: every scan is counted) or 301 (browsers may cache the hop). |
link.disabled | boolean | A disabled link answers 404 until it is enabled again. |
link.held | boolean | True while the link is switched off for a safety review. A held link cannot be changed or enabled (403 forbidden, details.reason link_held); it can be deleted. |
link.expires_at | string, null | When the link stops sending people to url, or null. |
link.expired_url | string, null | Where the link sends people after it expires; null shows a plain 'link expired' page (410). Checked like a destination. |
link.expired | boolean | True once expires_at has passed. |
link.starts_at | string, null | When the link goes live: an ISO date-time, a minute to ten years ahead. Before it a visit gets a "not live yet" page (404) and is not counted, and saved copies stay private. Null removes it. Must come before expires_at. Free on every plan. |
link.scheduled | boolean | True while starts_at is still ahead. |
link.max_clicks | integer, null | Stop the link after this many counted visits in total (1 to 10,000,000), counting the clicks it already has. Counted means what clicks counts: bots, link previews and repeat taps from one network within a minute are not. The visit that reaches it still goes through; after it the link answers like an expired one (expired_url or a 410), within a minute everywhere. Raising or removing the limit reopens it. Null removes it. Free on every plan. |
link.limit_reached | boolean | True once the link reached max_clicks. |
link.track | boolean | False: visits are only counted (clicks still rises, max_clicks still applies), with no record of when, where, on what device or from which site. True (the default): every counted visit is recorded for analytics. Free on every plan. |
link.password_protected | boolean | True when visitors must enter a password first. The password is never returned. |
link.routes | array, null | Rules, or null. |
link.forward_query | boolean | When true, a visit's own query parameters (?ref=ig and the like) are added to the destination, replacing a parameter of the same name; Snip's qr marker is never passed on. Free on every plan. |
link.preview | LinkPreview | The link's own social preview: the title, line and picture chat apps and social sites (iMessage, WhatsApp, Slack, Discord, X, LinkedIn, Facebook and the like) show when the short link is pasted, in place of the destination's own. Any field left out falls back to the destination's. image is an https address of a picture, or screenshot for the page's first screen from the link's newest public saved copy. Null clears it. Free on every plan. |
link.health | object | The destination check: every link's destination is fetched every few days. failing after two failed checks in a row (a 404, a 410, a server error or no answer); sites that refuse automated visitors are not counted as failing. |
link.clicks | integer | Every click and scan since the link was made. |
link.qr_style | QrStyle | How the link's QR code is drawn in the dashboard and at its image URLs. |
link.qr_png_url | string | The link's QR code as PNG (512 px). Free, no key. |
link.qr_svg_url | string | The same code as SVG, for print. |
link.snapshots | public | private | off | Who can read the link's saved copies. public: anyone with the link, at the link's format endings (snip.link/abc.md, .pdf ...), and the first one asked for saves the page. private: only you, through the signed URLs the API returns. off: no copies are made. |
link.watch | Watch | The page watch: a fresh copy of the page daily or weekly, its words compared with the last look's, and an email to the owner listing what was added and what went when they differ. Null while the page is not watched. |
link.file | File | The file, when the link is a shared file (docs/drops.md): the link shows it instead of redirecting. Null for an ordinary link. |
link.stats_url | string, null | The link's public stats page (totals by day, country, referring site and device for the last 30 days), when the owner shared it with stats_public; null while the stats are private. |
link.created_at | string | |
link.updated_at | string |
Errors
| Status | Meaning |
|---|---|
| 400 | The body or query is invalid. details names each field. |
| 401 | The API key is missing, malformed or revoked. |
| 403 | The plan does not include this. details.upgrade names the plan that does. |
| 409 | The ending is taken or reserved. Pick another. |
| 413 | The input is larger than the plan allows per run. details carries the size, the limit and the unit. |
| 429 | Either the key exceeded its requests-per-second limit (honor Retry-After), or a daily or plan quota is used up (details.reason). |