---
title: "Create a short link"
method: POST
path: "/v1/links"
tags: ["Links"]
---

# Create a short link

`POST /v1/links`

## Request

- Base URL: `https://xn--s7y.xn--3ds443g`
- URL: `https://xn--s7y.xn--3ds443g/v1/links`
- Auth: one of:
  - none
  - HTTP bearer
  - API key in cookie `duan_session`

Creates a short URL. Anonymous callers share rate limits with the web create form. Session cookie and API key are optional for basic create; higher capacity may require an API key when issued. Request body must include **exactly one** of `url` or `longUrl` (OpenAPI `oneOf`). Optional `alias` / `customSlug`: if both are sent they must match (case-insensitive); otherwise **409** `FIELD_CONFLICT`. User aliases are folded to lowercase before persistence. An OAuth access token is not an API key here: presenting one is 401 and does not fall through to anonymous create.

## Headers

- `Idempotency-Key` string, required
- `X-Duan-Client-Id` string
- `X-Request-Id` string

## Request body

- union
  - object
    - `url` string, uri, required: Absolute http or https URL for create, batch, and PATCH destinations. Scheme is lowercase http:// or https:// with no surrounding whitespace.
    - `alias` string, nullable: Optional custom short code. Stored lowercase; lookup is case-insensitive. Must not be reserved.
    - `customSlug` string, nullable: Compatibility alias of `alias`. If both are set they must match (case-insensitive) or the API returns 409 FIELD_CONFLICT.
    - `source` string: Caller hint only — not trusted click attribution.
    - `title` string
    - `locale` union: API language preference. Canonical values: en and zh-CN. Documented aliases normalized to the canonical value: zh-cn, zh-Hans, zh-hans, zh_Hans, zh_hans. Traditional Chinese is not supported. EN, ZH-CN, and bare zh are rejected. Omitted or blank defaults to en. This preference does not rewrite URL bytes, translate parameter names, or use the caller's location. Website routes stay /zh-cn.
      - 'en' | 'zh-CN' | 'zh-cn' | 'zh-Hans' | 'zh-hans' | 'zh_Hans' | 'zh_hans'
      - string
  - object
    - `longUrl` string, uri, required: Absolute http or https URL for create, batch, and PATCH destinations. Scheme is lowercase http:// or https:// with no surrounding whitespace.
    - `alias` string, nullable: Optional custom short code. Stored lowercase; lookup is case-insensitive. Must not be reserved.
    - `customSlug` string, nullable: Compatibility alias of `alias`. If both are set they must match (case-insensitive) or the API returns 409 FIELD_CONFLICT.
    - `source` string: Caller hint only — not trusted click attribution.
    - `title` string
    - `locale` union: API language preference. Canonical values: en and zh-CN. Documented aliases normalized to the canonical value: zh-cn, zh-Hans, zh-hans, zh_Hans, zh_hans. Traditional Chinese is not supported. EN, ZH-CN, and bare zh are rejected. Omitted or blank defaults to en. This preference does not rewrite URL bytes, translate parameter names, or use the caller's location. Website routes stay /zh-cn.
      - 'en' | 'zh-CN' | 'zh-cn' | 'zh-Hans' | 'zh-hans' | 'zh_Hans' | 'zh_hans'
      - string

## Response `200`

Idempotent replay of a prior successful create (see Idempotency-Replayed)

- union
  - ActiveLinkCreated
    - `id` string, required: Short code
    - `state` 'active', required
    - `short_url` string, uri, required: Unicode brand host form for humans
    - `short_url_ascii` string, uri, required: Punycode host form — prefer for machine clients
    - `destination` string, uri, required
    - `qr_tracking_url` string, uri, required: Tagged short URL encoded into QR images (?s=qr). Image bytes: GET /v1/links/{id}/qr (PNG default, or ?format=svg).
    - `qr_url` string, uri: Deprecated synonym of qr_tracking_url (optional in 1.4.0; prefer qr_tracking_url)
    - `created_at` string, date-time, required
    - `status_url` string, uri, required: Absolute URL for GET /v1/links/{id}
    - `request_id` string, required: Same value as the X-Request-Id response header: the inbound X-Request-Id if it matches [A-Za-z0-9._-]{1,128}, otherwise a fresh server-generated id.
  - NonActiveLinkCreated: Quarantined or other non-active create outcome. short_url, short_url_ascii, destination, and qr_tracking_url are omitted.
    - `id` string, required: Short code
    - `state` 'quarantined' | 'disabled' | 'blocked' | 'expired', required
    - `created_at` string, date-time, required
    - `status_url` string, uri, required: Absolute URL for GET /v1/links/{id}
    - `request_id` string, required: Same value as the X-Request-Id response header: the inbound X-Request-Id if it matches [A-Za-z0-9._-]{1,128}, otherwise a fresh server-generated id.
    - `error` 'LINK_QUARANTINED'
    - `message` string

## Other responses

- `201`: Active link created (ActiveLinkCreated)
- `202`: Created but non-active (typically quarantined). NonActiveLinkCreated — destination/short URLs omitted. Poll status_url.
- `400`: INVALID_URL | RESERVED_ALIAS | IDEMPOTENCY_KEY_REQUIRED | VALIDATION_ERROR | FIELD_CONFLICT (url/longUrl both set)
- `401`: AUTH_REQUIRED — reserved for authenticated-only operations (not anonymous create)
- `403`: BLOCKED_DESTINATION
- `409`: ALIAS_TAKEN | IDEMPOTENCY_CONFLICT | IDEMPOTENCY_IN_PROGRESS | FIELD_CONFLICT (alias≠customSlug)
- `429`: RATE_LIMITED
- `503`: INTERNAL_ERROR

---

[API](https://skmtc.dev/xn--s7y/apis/agent-api.md) · [All operations](https://skmtc.dev/xn--s7y/apis/agent-api/llms.txt) · [OpenAPI document](https://skmtc.dev/xn--s7y/apis/agent-api/revisions/d902f68518e9?raw)
