HTTP API
REST at https://api.linkdash.dev/v1 with a bearer key from the dashboard. Same operations as the CLI and MCP.
Base URL: https://api.linkdash.dev/v1
Auth: Authorization: Bearer ldash_... (create keys at Dashboard → API keys). Do not invent keys.
CLI reference: CLI. MCP: MCP. Pricing / quotas: Pricing. Discovery (no auth): GET https://api.linkdash.dev/v1. Plain markdown: api.md.
Public ids are slugs (deck, k3m9xq), not Convex _id. JSON field names match CLI stdout (id, url, kind, …).
Conventions
- JSON
Content-Type: application/jsonunless noted. - Create takes exactly one of
url,text, orstorageIdafterPOST /uploads. Mint returns{ "uploadUrl", "storageId" }. The host PUTs bytes touploadUrl. Small files may still use multipartfileonPOST /links. - Durations:
7d,24h,30m,1w, or an ISO date. Fields:expires/expiresAt,starts/startsAt. - Openings limit: optional positive integer
maxOpenings. The link pauses after that many successful openings. RaisemaxOpenings, orclearMaxOpenings, to serve it again. OmitmaxOpeningsfor unlimited. - If a slug exists on two of the owner’s domains, pass
?domain=/ JSONdomain. - Errors:
{ "error": "..." }plus optionalcode. Takenlida.shslugs return409withcode: "slug_taken". - Writes count toward the monthly event quota. List / get / stats / export / webhook list/get do not. Visit / download / play ingest still count.
Endpoints
| CLI | HTTP | MCP |
|---|---|---|
auth whoami |
GET /me |
whoami |
links list |
GET /links (?q=&tag=&kind=&status=) |
links_list |
links get <id> |
GET /links/:id (?domain=) |
links_get |
links create --url/--file/--text |
POST /links |
links_create |
links update <id> |
PATCH /links/:id |
links_update |
links delete <id> |
DELETE /links/:id |
links_delete |
links stats --last 24h |
GET /links/:id/stats?last=24h |
links_stats |
links export |
GET /links/export |
links_export |
links import |
POST /links/import |
links_import |
| (host PUT after mint) | POST /uploads |
files_upload_url |
DELETE /uploads/:storageId |
files_discard |
|
webhooks list/create/get/rotate/delete |
GET/POST /webhooks, GET/DELETE /webhooks/:id, POST /webhooks/:id/rotate |
webhooks_* |
domains list/add/connect/verify/remove |
GET/POST /domains, GET /domains/:host/connect, POST /domains/:host/verify, DELETE /domains/:host |
domains_* |
auth create, auth login, and auth logout are browser device flows. They are not Unkey routes.
GET /me
Signed-in account. { "userId": "...", "email": "...", "name": "..." }. Email and name are omitted when unknown.
Create a URL link
curl https://api.linkdash.dev/v1/links \
-H "Authorization: Bearer ldash_..." \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","slug":"welcome"}'
JSON body takes exactly one of url, text, or storageId, plus optional slug, name, tags, starts, expires, maxOpenings, password, domain, previewName, previewDescription, previewImageStorageId, and utm (URL links only).
Upload a file
Mint an upload URL ({uploadUrl,storageId}), PUT the bytes from the host, then create the link in the same flow:
curl https://api.linkdash.dev/v1/uploads \
-H "Authorization: Bearer ldash_..." \
-H "Content-Type: application/json" \
-d '{"fileSize":12345}'
curl -X PUT -H "Content-Type: application/pdf" --data-binary @./deck.pdf "$uploadUrl"
curl https://api.linkdash.dev/v1/links \
-H "Authorization: Bearer ldash_..." \
-H "Content-Type: application/json" \
-d '{"storageId":"…","fileName":"deck.pdf","fileType":"application/pdf","fileSize":12345}'
Small files can still use multipart on POST /links:
curl https://api.linkdash.dev/v1/links \
-H "Authorization: Bearer ldash_..." \
-F "file=@./deck.pdf" \
-F "expires=7d" \
-F "slug=deck"
Optional multipart fields: slug, name, tags, starts / startsAt, password, expires, maxOpenings, domain, previewName, previewDescription, previewImage.
Update, stats, CSV
PATCH /links/:id is JSON or multipart. Replace file bytes with storageId after POST /uploads + host PUT, or multipart field file. --status / { "status": "expired" } is rejected — use expires. { "status": "scheduled" } is rejected — use starts. Optional maxOpenings pauses the link after that many openings; clearMaxOpenings removes the limit.
GET /links/:id/stats?last=24h returns visits, downloads, plays, visitsInLast, countries, referrers, and visitors.
GET /links/export is CSV. POST /links/import accepts raw CSV or multipart file.
Errors
| Status | When |
|---|---|
| 400 | Validation, missing body, mixed create kinds |
| 401 | Missing or invalid bearer key |
| 404 | Unknown slug or domain |
| 409 | Slug taken (code: "slug_taken") |
| 429 | Monthly event quota reached |
| 503 | API keys not configured on this deployment |
See webhooks and custom domains for those endpoints.

