---
name: linkdash
description: Create and manage Linkdash short links from the linkdash CLI, HTTP API, or MCP: URLs, file uploads, text snippets, analytics, pause/resume via update, delete, custom domains, webhooks, CSV import/export, and API keys. Use when the user mentions Linkdash, lida.sh, linkdash.dev, short links, tracked file sharing, or running linkdash in a terminal or agent workflow.
---

# Linkdash CLI

Install the CLI, then run commands. Successful commands print JSON on stdout. Help and errors are plain text on stderr (exit code 1).

```bash
npm i -g linkdash-cli
# or: pnpm add -g linkdash-cli / bun add -g linkdash-cli / npx linkdash-cli@latest
```

The command is `linkdash`.

## Auth

No account yet (opens https://linkdash.dev/sign-up, then /device):

```bash
linkdash auth create
linkdash auth whoami
linkdash auth logout
```

Already have an account (opens https://linkdash.dev/device):

```bash
linkdash auth login
```

Headless CLI: set `LINKDASH_TOKEN` to a session token from `auth create` or `auth login`. Do not invent tokens; if login is required, run `auth create` / `auth login` or ask the user.

HTTP API and MCP: create a bearer key at https://linkdash.dev/dashboard/keys (prefix `ldash_`). Do not invent keys. MCP: https://api.linkdash.dev/mcp. REST: https://api.linkdash.dev/v1.

Local/non-prod:

```bash
export LINKDASH_SITE_URL=https://linkdash.dev
export LINKDASH_CONVEX_URL=https://<deployment>.convex.cloud
export LINKDASH_CONVEX_SITE_URL=https://<deployment>.convex.site
```

Session file: `~/.config/linkdash/config.json` (mode 0600).

The CLI cannot change account email, password, or billing. Send the user to https://linkdash.dev/dashboard/billing or https://linkdash.dev/dashboard/settings. API keys are managed at https://linkdash.dev/dashboard/keys.

## Create (exactly one kind)

A create command takes **exactly one** of `--url`, `--file`, or `--text`. Mixing them fails.

Slug is the public path. Name is only visible to you.

```bash
linkdash links create --url https://example.com/q4/deck.pdf
linkdash links create --url https://example.com --slug welcome --name "Homepage"
linkdash links create --url https://example.com --utm-source twitter --utm-medium social --utm-campaign q4
linkdash links create --file ./deck.pdf --expires 7d --starts 1d
linkdash links create --url https://example.com --max-openings 1
linkdash links create --text "release checklist" --tag launch --tag q4
```

Optional on any create: `--slug`, `--name`, `--tag` (repeatable), `--starts 7d|24h|30m|<ISO date>`, `--password`, `--expires 7d|24h|30m|<ISO date>`, `--max-openings <n>`, `--domain <host>`, `--preview-name`, `--preview-description`, `--preview-image`.

UTM flags (`--utm-source`, `--utm-medium`, `--utm-campaign`, `--utm-term`, `--utm-content`) apply **only** to `--url`.

Public ids default to generated 6-character slugs (`a-z0-9`). Custom slugs are lowercase letters, numbers, and internal hyphens (1–64). Default host is `lida.sh`. Uniqueness is per host: `lida.sh/welcome` is shared; each custom domain has its own namespace. If `lida.sh/{slug}` is taken, add a custom domain and retry with `--domain`.

## Other link commands

```bash
linkdash links list --q deck --tag launch --kind file --status paused
linkdash links get <id>
linkdash links get welcome --domain go.example.com
linkdash links update <id> --url https://example.com/new
linkdash links update <id> --file ./new.pdf
linkdash links update <id> --status paused
linkdash links update <id> --status active
linkdash links update <id> --max-openings 5
linkdash links update <id> --clear-max-openings
linkdash links delete <id>
linkdash links stats <id> --last 24h
linkdash links export > links.csv
linkdash links import links.csv
```

`update` also accepts `--status active|paused`, `--file` (replace bytes, same slug), `--password`, `--expires`, `--starts`, `--max-openings`, `--domain`, `--text`, `--slug`, `--name`, `--tag`, `--preview-*`, `--utm-*`, `--clear-password`, `--clear-expires`, `--clear-starts`, `--clear-tags`, `--clear-preview`, `--clear-max-openings`. `--url` repoints a URL link. `--status` only accepts `active` or `paused`; expired comes from `--expires`; scheduled comes from `--starts`. After `--max-openings` is reached the link pauses; raise the number or `--clear-max-openings` to serve it again.

`--last` on stats is a duration (`24h`, `7d`). Default `24h`. Stats JSON includes `visits`, `downloads`, `plays`, `visitsInLast`, `countries`, `referrers`, and `visitors`.

CSV columns: `slug,kind,name,url,text,fileName,status,domain,tags,starts,expires,maxOpenings,created`. Import creates/updates URL rows by slug. File rows without bytes are skipped.

## Webhooks

HTTPS endpoints for `link.visit`, `link.download`, and `link.play`. Up to 10 per account. Secret is shown once (`whsec_…`). Keep it and verify Standard Webhooks signatures. Never put the secret in the JSON body.

```bash
linkdash webhooks create --url https://example.com/hooks
linkdash webhooks list
linkdash webhooks get <id>
linkdash webhooks rotate <id>
linkdash webhooks delete <id>
```

Headers: `svix-id`, `svix-timestamp`, `svix-signature`. Verify with `svix` (`npm i svix`). Use the raw body.

```ts
import { Webhook, WebhookVerificationError } from "svix";

const wh = new Webhook(process.env.LINKDASH_WEBHOOK_SECRET); // whsec_… shown once

try {
  const event = wh.verify(rawBody, {
    "svix-id": headers["svix-id"],
    "svix-timestamp": headers["svix-timestamp"],
    "svix-signature": headers["svix-signature"],
  });
} catch (err) {
  if (err instanceof WebhookVerificationError) {
    // 400 — do not process
  }
  throw err;
}
```

## Domains

Free includes 1 custom domain. Professional has 5. Growth is unlimited.

```bash
linkdash domains list
linkdash domains add docs.example.com
linkdash domains connect docs.example.com
linkdash domains verify docs.example.com
linkdash domains remove docs.example.com
```

After add, point DNS as instructed (CNAME or apex ALIAS plus a TXT Unkey returns), then verify. `domains connect` returns a Domain Connect `applyUrl` when Unkey can one-click at the DNS provider. Open that URL in a browser. Records from add/list still work for every other provider. Assign with `--domain` on create/update. Generated ids and `lida.sh` slugs share one public namespace. Custom domains have their own.

## HTTP API and MCP

Base URL: `https://api.linkdash.dev/v1`. MCP: `https://api.linkdash.dev/mcp`. Auth: `Authorization: Bearer ldash_...`. Full reference: https://linkdash.dev/api.md. MCP: https://linkdash.dev/mcp.md. CLI: https://linkdash.dev/cli.md. Pricing: https://linkdash.dev/pricing.md.

| 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` | `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 `/v1` or MCP tools.

`POST /links` JSON takes exactly one of `url`, `text`, or `storageId`. Files: `POST /uploads` with `{ "fileSize" }` returns `{ "uploadUrl", "storageId" }`. The host PUTs bytes to `uploadUrl`, then `POST /links` with that `storageId`, `fileName`, `fileType`, `fileSize` in the same flow. Small files may use multipart `file` on `POST /links`. Optional on create: `slug`, `name`, `tags`, `starts`/`startsAt`, `password`, `expires` (`7d`), `maxOpenings`, `domain`, `previewName`, `previewDescription`, `previewImageStorageId`, `utm` (URL only). `PATCH /links/:id` accepts `status`: `active` or `paused` (not `expired`), `maxOpenings`, `clearMaxOpenings`, and `storageId` or multipart `file` to replace bytes. If a slug is used on two of the owner's domains, pass `?domain=` / `--domain`.

Every create/update/delete, domain add/verify/remove, and webhook create/rotate/delete counts toward the monthly event quota, same as a visit. Reads (list/get/stats/export/webhook list/get/domain connect) do not. Visit/download/play ingest still increment the monthly event quota.

## Agent rules

1. Use the CLI, the documented HTTP API, or MCP. Do not invent endpoints, flags, or tokens.
2. Parse CLI stdout as JSON. Do not pass `--json` (it does not exist).
3. Never confirm deletes unless the user asked for a confirmation step.
4. Never put secrets in command examples you echo back (`--password`, tokens, API keys, webhook secrets). Do not print `uploadUrl` to the user.
5. If a command fails, read the stderr or JSON `error` and fix flags; do not retry with `ls`, `rm`, `shorten`, or `--json`.
6. Product site: https://linkdash.dev — agent overview: https://linkdash.dev/llms.txt — pricing: https://linkdash.dev/pricing.md — CLI: https://linkdash.dev/cli.md — HTTP API: https://linkdash.dev/api.md — MCP: https://linkdash.dev/mcp.md
