# Sink

> Sink short link API operations via OpenAPI. Use when managing short links: creating, querying, updating, deleting, listing, importing, exporting links, configuring smart routing, password protection, unsafe-link warnings, and analytics exports. Also covers AI-powered slug and OpenGraph metadata generation. Triggers: "create short link", "shorten URL", "delete link", "edit link", "list links", "export links", "import links", "link analytics", "export analytics", "AI slug", "AI OpenGraph", "geo routing".

- Skill: `miantiao-me/sink` (Agent Skill)
- Install (CLI): `npx skillmds@latest add miantiao-me/sink`
- Raw SKILL.md: https://api.skillmd.com/api/skills/miantiao-me/sink/raw
- Safety review: PASS (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: miantiao-me (https://skillmd.com/u/miantiao-me)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/miantiao-me/sink

---


# Sink API

Sink is a link shortener running on Cloudflare. Manage links via REST API.

## Authentication

All endpoints require Bearer token authentication:

```http
Authorization: Bearer YOUR_SITE_TOKEN
```

Token = `NUXT_SITE_TOKEN` environment variable.

## Base URL

```
https://your-sink-domain
```

## API Reference

### Create Link

```http
POST /api/link/create
Content-Type: application/json

{
  "url": "https://example.com/long-url",
  "slug": "custom-slug",
  "comment": "optional note",
  "expiration": 1735689599,
  "apple": "https://apps.apple.com/app/id123",
  "google": "https://play.google.com/store/apps/details?id=com.example",
  "geo": {
    "US": "https://example.com/us"
  },
  "title": "Example Title",
  "description": "Example social preview description",
  "password": "optional-password",
  "redirectWithQuery": true
}
```

**Required**: `url`
**Optional**: `slug` (auto-generated if omitted), `comment`, `expiration` (unix timestamp), `apple` (Apple device redirect), `google` (Android redirect), `geo` (country-specific routing map), `password`, `unsafe`, `title`, `description`, `image`, `cloaking`, `redirectWithQuery`

> If `NUXT_SAFE_BROWSING_DOH` is configured and `unsafe` is not explicitly set, the server auto-detects via DoH and marks unsafe links automatically.

**Response** (201):

```json
{
  "link": {
    "id": "abc123",
    "url": "https://example.com/long-url",
    "slug": "custom-slug",
    "createdAt": 1718119809,
    "updatedAt": 1718119809
  },
  "shortLink": "https://your-domain/custom-slug"
}
```

**Errors**: 409 (slug exists)

### Query Link

```http
GET /api/link/query?slug=custom-slug
```

**Response** (200):

```json
{
  "id": "abc123",
  "url": "https://example.com",
  "slug": "custom-slug",
  "createdAt": 1718119809,
  "updatedAt": 1718119809
}
```

**Errors**: 404 (not found)

### Edit Link

```http
PUT /api/link/edit
Content-Type: application/json

{
  "slug": "existing-slug",
  "url": "https://new-url.com",
  "comment": "updated note"
}
```

**Required**: `slug` (identifies which link to edit), `url`
**Optional**: other fields to update

**Response** (201): Same as create

**Errors**: 404 (not found)

### Delete Link

```http
POST /api/link/delete
Content-Type: application/json

{
  "slug": "slug-to-delete"
}
```

**Response**: 200 (empty body)

### List Links

```http
GET /api/link/list?limit=20&cursor=abc123
```

**Parameters**:

- `limit`: max 1024, default 20
- `cursor`: pagination cursor from previous response

**Response**:

```json
{
  "keys": [],
  "list_complete": false,
  "cursor": "next-cursor"
}
```

### Export Links

```http
GET /api/link/export
```

**Response**:

```json
{
  "version": "1.0",
  "exportedAt": "2024-01-01T00:00:00Z",
  "count": 100,
  "links": [],
  "list_complete": true
}
```

### Import Links

```http
POST /api/link/import
Content-Type: application/json

{
  "links": [
    {"url": "https://example1.com", "slug": "ex1"},
    {"url": "https://example2.com", "slug": "ex2"}
  ]
}
```

**Response**: imported links array

### AI Slug Generation

```http
GET /api/link/ai?url=https://example.com/article
```

The server can use the URL and extracted page content to generate a readable slug.

**Response**:

```json
{
  "slug": "ai-generated-slug"
}
```

### AI OpenGraph Metadata Generation

```http
GET /api/link/og-ai?url=https://example.com/article&locale=en-US
```

Generates a localized OpenGraph title and description from the URL and extracted page content.

**Response**:

```json
{
  "title": "Example Article",
  "description": "A concise social preview description."
}
```

### Verify Token

```http
GET /api/verify
```

Verify if the site token is valid.

**Response** (200):

```json
{
  "name": "Sink",
  "url": "https://sink.cool"
}
```

**Errors**: 401 (invalid token)

## Link Fields

| Field               | Type    | Required | Description                                                                          |
| ------------------- | ------- | -------- | ------------------------------------------------------------------------------------ |
| `url`               | string  | Yes      | Target URL (max 2048)                                                                |
| `slug`              | string  | No       | Custom slug (auto-generated)                                                         |
| `comment`           | string  | No       | Internal note                                                                        |
| `expiration`        | number  | No       | Unix timestamp                                                                       |
| `apple`             | string  | No       | iOS/macOS redirect URL                                                               |
| `google`            | string  | No       | Android redirect URL                                                                 |
| `geo`               | object  | No       | Country-specific routing map, for example `{ "US": "https://example.com/us" }`       |
| `title`             | string  | No       | Custom title (max 256)                                                               |
| `description`       | string  | No       | Custom description                                                                   |
| `image`             | string  | No       | Custom image path                                                                    |
| `cloaking`          | boolean | No       | Enable link cloaking                                                                 |
| `redirectWithQuery` | boolean | No       | Append query params to destination URL (overrides global `NUXT_REDIRECT_WITH_QUERY`) |
| `password`          | string  | No       | Password protection for the link                                                     |
| `unsafe`            | boolean | No       | Mark as unsafe (shows warning page before redirect)                                  |

## Analytics Endpoints

### Counters

```http
GET /api/stats/counters
```

### Metrics

```http
GET /api/stats/metrics
```

### Views

```http
GET /api/stats/views
```

### Heatmap

```http
GET /api/stats/heatmap
```

### Export Access Analytics

```http
GET /api/stats/export?startAt=1717200000&endAt=1719791999&slug=custom-slug
```

Returns `text/csv` with `slug`, `url`, `viewer`, `views`, and `referer` columns.

## OpenAPI Docs

- JSON: `/_docs/openapi.json`
- Scalar UI: `/_docs/scalar`
- Swagger UI: `/_docs/swagger`

## cURL Examples

Create link:

```bash
curl -X POST https://your-domain/api/link/create \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://github.com/example"}'
```

List links:

```bash
curl https://your-domain/api/link/list \
  -H "Authorization: Bearer YOUR_TOKEN"
```

Delete link:

```bash
curl -X POST https://your-domain/api/link/delete \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"slug": "my-slug"}'
```

