---
title: "Create short links (sync batch)"
method: POST
path: "/v1/links/batch"
tags: ["Links"]
---

# Create short links (sync batch)

`POST /v1/links/batch`

## Request

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

Sync batch create. Body `items` length 1–100. Each item requires `client_ref` (unique within the batch) plus destination (`url` XOR `longUrl`) and optional `alias`. Partial success: HTTP 200 envelope with per-item `ok` + `result` (ActiveLinkCreated | NonActiveLinkCreated) or `ok: false` + `problem` + `retryable`. Correlate by `client_ref` only — never by array index. Over 100 items → 400 BATCH_TOO_LARGE (`/problems/batch-too-large`). Whole-batch `Idempotency-Key` required (scoped as `batch:{key}`). Same auth / abuse / destination safety rules as single create. Missing scope → **403** `INSUFFICIENT_SCOPE`. Accepted credentials are anonymous access, a session cookie, or an API key with `batches:create`. An OAuth access token is not accepted. 批量重试只有三种情况，不要混用。示例：key 为 batch-1，两项 client_ref 分别为 notes（https://example.com/notes）和 draft（https://example.com/draft）。notes 成功，draft 的 retryable 为 true。(1) 传输结果不确定（没有拿到已完成的响应、IDEMPOTENCY_IN_PROGRESS、幂等锁不可用，或因瞬时数据库错误且一条都未创建而返回 503）：用同一个 key 原样重发整批。已完成的批次返回已保存的结果，notes 不会再次创建。key 已被释放时，同一批会重新执行。(2) 已完成且某项 retryable 为 true：重放 batch-1 只返回已保存的结果，不会重试 draft。只用新 key（例如 batch-1-draft）发送 draft，并保留 client_ref draft，不要带上 notes。去掉 notes 后仍用 batch-1 会返回 IDEMPOTENCY_CONFLICT。(3) 校验错误：改正请求，不要把同一份无效请求当作网络重试。key 尚未保存的 400（重复 client_ref、缺少 items、BATCH_TOO_LARGE）改正后可以继续用同一个 key。已完成且 retryable 为 false 的项已保存；修改请求体后继续使用原 key，会返回 IDEMPOTENCY_CONFLICT，改正后的项必须用新 key。 Batch retry has three cases; do not mix them. Worked example: key batch-1 with client_ref notes (https://example.com/notes) and draft (https://example.com/draft). notes succeeds and draft is retryable true. (1) Uncertain transport outcome — no completed body, IDEMPOTENCY_IN_PROGRESS, an unavailable idempotency lock, or 503 because a transient database error created nothing: resend the entire unchanged batch with the same key. A completed batch returns the stored result and notes is not created again. A released key runs that same batch again. (2) Completed batch with retryable true: replaying batch-1 returns the stored body and does not retry draft. Send only draft, keeping client_ref draft, under a new key such as batch-1-draft. Do not include notes. Dropping notes and reusing batch-1 returns IDEMPOTENCY_CONFLICT. (3) Validation error: fix the request. Do not resend the same invalid body as a network retry. A 400 before the key is stored (duplicate client_ref, missing items, BATCH_TOO_LARGE) may reuse the key after the body is corrected. A completed item with retryable false is stored; a changed body on that key returns IDEMPOTENCY_CONFLICT, so send the corrected item with a new key.

## Headers

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

## Request body

- BatchCreateLinksRequest
  - `items` BatchCreateLinkItem[], required: Sync batch items (1–100). Over 100 rejected as BATCH_TOO_LARGE.
    - union: Requires `client_ref` and exactly one of `url` or `longUrl`. Prefer `url`. `client_ref` is the only correlation key for results.
      - object
        - `client_ref` string, required: Client correlation id — unique within the batch; never use array index
        - `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
        - `customSlug` string, nullable
        - `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
        - `client_ref` string, required: Client correlation id — unique within the batch; never use array index
        - `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
        - `customSlug` string, nullable
        - `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`

Batch processed (may be partial success). Results keyed by client_ref inside each item — do not use array index for joins.

- BatchCreateLinksResponse
  - `batch_id` string, uuid, required
  - `batch_mode` 'sync', required
  - `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.
  - `ok_count` integer, required
  - `fail_count` integer, required
  - `results` BatchCreateLinkResultItem[], required: Per-item outcomes. Join only on client_ref — never by array index.
    - union
      - BatchCreateLinkItemSuccess
        - `client_ref` string, required
        - `ok` true, required
        - `result` union, required
          - 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
      - BatchCreateLinkItemFailure
        - `client_ref` string, required
        - `ok` false, required
        - `retryable` boolean, required: true for RATE_LIMITED, INTERNAL_ERROR, IDEMPOTENCY_IN_PROGRESS; false otherwise
        - `problem` ProblemDetails, required
          - `type` string, uri, required: RFC 9457 type URI under /problems/{slug}
          - `title` string, required
          - `status` integer, required
          - `detail` string, required
          - `instance` string, required: RFC 9457 instance URI for this occurrence
          - `error` 'INVALID_URL' | 'BLOCKED_DESTINATION' | 'RATE_LIMITED' | 'ALIAS_TAKEN' | 'RESERVED_ALIAS' | 'AUTH_REQUIRED' | 'LINK_QUARANTINED' | 'LINK_DISABLED' | 'LINK_BLOCKED' | 'LINK_EXPIRED' | 'LINK_NOT_FOUND' | 'IDEMPOTENCY_KEY_REQUIRED' | 'IDEMPOTENCY_CONFLICT' | 'IDEMPOTENCY_IN_PROGRESS' | 'INTERNAL_ERROR' | 'VALIDATION_ERROR' | 'FIELD_CONFLICT' | 'EMAIL_UNVERIFIED' | 'BATCH_TOO_LARGE' | 'INSUFFICIENT_SCOPE', required: Legacy machine code (same as code)
          - `message` string, required: Legacy human message (same as detail)
          - `code` 'INVALID_URL' | 'BLOCKED_DESTINATION' | 'RATE_LIMITED' | 'ALIAS_TAKEN' | 'RESERVED_ALIAS' | 'AUTH_REQUIRED' | 'LINK_QUARANTINED' | 'LINK_DISABLED' | 'LINK_BLOCKED' | 'LINK_EXPIRED' | 'LINK_NOT_FOUND' | 'IDEMPOTENCY_KEY_REQUIRED' | 'IDEMPOTENCY_CONFLICT' | 'IDEMPOTENCY_IN_PROGRESS' | 'INTERNAL_ERROR' | 'VALIDATION_ERROR' | 'FIELD_CONFLICT' | 'EMAIL_UNVERIFIED' | 'BATCH_TOO_LARGE' | 'INSUFFICIENT_SCOPE', required
          - `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.

## Other responses

- `400`: BATCH_TOO_LARGE | VALIDATION_ERROR | INVALID_URL | IDEMPOTENCY_KEY_REQUIRED | FIELD_CONFLICT | RESERVED_ALIAS
- `403`: INSUFFICIENT_SCOPE
- `409`: IDEMPOTENCY_CONFLICT | IDEMPOTENCY_IN_PROGRESS
- `429`: RATE_LIMITED (request-level; item-level rate limits appear in results)
- `503`: INTERNAL_ERROR (idempotency lock unavailable)

---

[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)
