Skip to content
LinkdashLinkdash
Esc
navigateopen⌘Jpreview
On this page

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/json unless noted.
  • Create takes exactly one of url, text, or storageId after POST /uploads. Mint returns { "uploadUrl", "storageId" }. The host PUTs bytes to uploadUrl. Small files may still use multipart file on POST /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. Raise maxOpenings, or clearMaxOpenings, to serve it again. Omit maxOpenings for unlimited.
  • If a slug exists on two of the owner’s domains, pass ?domain= / JSON domain.
  • Errors: { "error": "..." } plus optional code. Taken lida.sh slugs return 409 with code: "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.

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.

Was this page helpful?