{
  "openapi": "3.1.0",
  "info": {
    "title": "短.在线 Agent API",
    "description": "短.在线 agent API for creating short links. Prefer this over automating the HTML form. Requires Idempotency-Key on POST /v1/links. Session cookie auth is optional (anonymous create allowed). v1 is the current stable surface; breaking changes will be introduced under a new path prefix, not by silently changing /v1. Caller-supplied `source` is a client hint only — it is not trusted analytics attribution. Error bodies keep `error` + `message` and also include RFC 9457 fields (`type`, `title`, `status`, `detail`, `request_id`).",
    "version": "1.0.0",
    "contact": {
      "name": "短.在线",
      "url": "https://短.在线/security"
    }
  },
  "servers": [
    {
      "url": "https://xn--s7y.xn--3ds443g",
      "description": "Production apex (punycode)"
    },
    {
      "url": "https://短.在线",
      "description": "Production apex (Unicode IDN)"
    }
  ],
  "security": [
    {},
    {
      "sessionCookie": []
    }
  ],
  "paths": {
    "/v1/links": {
      "post": {
        "operationId": "createLink",
        "summary": "Create a short link",
        "description": "Creates a short URL. Anonymous callers share the same rate limits as POST /api/links. Session cookie auth is optional. Idempotency-Key is required.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Client-generated UUID/string. Retries with the same key and body replay the original response. Same key with a different body returns IDEMPOTENCY_CONFLICT.",
            "schema": {
              "type": "string",
              "maxLength": 256,
              "example": "550e8400-e29b-41d4-a716-446655440000"
            }
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Optional client correlation id. Server generates one when omitted and always returns it.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateLinkRequest"
              },
              "examples": {
                "basic": {
                  "value": {
                    "url": "https://example.com/a/very/long/path",
                    "alias": null,
                    "source": "agent",
                    "locale": "en"
                  }
                },
                "compatLongUrl": {
                  "value": {
                    "longUrl": "https://example.com/compat",
                    "source": "agent"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Link created",
            "headers": {
              "X-Request-Id": {
                "description": "Correlation id for this response. Echoes inbound X-Request-Id when provided.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateLinkResponse"
                }
              }
            }
          },
          "200": {
            "description": "Idempotent replay of a prior 201",
            "headers": {
              "Idempotency-Replayed": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              },
              "X-Request-Id": {
                "description": "Correlation id for this response. Echoes inbound X-Request-Id when provided.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateLinkResponse"
                }
              }
            }
          },
          "202": {
            "description": "Link created but quarantined (LINK_QUARANTINED). Poll GET /v1/links/{id}.",
            "headers": {
              "X-Request-Id": {
                "description": "Correlation id for this response. Echoes inbound X-Request-Id when provided.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateLinkQuarantinedResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation / missing Idempotency-Key / reserved alias / invalid alias or locale",
            "headers": {
              "X-Request-Id": {
                "description": "Correlation id for this response. Echoes inbound X-Request-Id when provided.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "idempotencyRequired": {
                    "value": {
                      "error": "IDEMPOTENCY_KEY_REQUIRED",
                      "message": "Idempotency-Key header is required"
                    }
                  },
                  "invalidUrl": {
                    "value": {
                      "error": "INVALID_URL",
                      "message": "url is not a valid absolute URL"
                    }
                  },
                  "reserved": {
                    "value": {
                      "error": "RESERVED_ALIAS",
                      "message": "alias is reserved"
                    }
                  }
                }
              },
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required (AUTH_REQUIRED). Not returned for anonymous create; reserved for authenticated-only operations.",
            "headers": {
              "X-Request-Id": {
                "description": "Correlation id for this response. Echoes inbound X-Request-Id when provided.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Blocked destination",
            "headers": {
              "X-Request-Id": {
                "description": "Correlation id for this response. Echoes inbound X-Request-Id when provided.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "BLOCKED_DESTINATION",
                  "message": "destination blocked by abuse policy"
                }
              }
            }
          },
          "409": {
            "description": "Alias taken or idempotency conflict",
            "headers": {
              "X-Request-Id": {
                "description": "Correlation id for this response. Echoes inbound X-Request-Id when provided.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "aliasTaken": {
                    "value": {
                      "error": "ALIAS_TAKEN",
                      "message": "alias is already taken"
                    }
                  },
                  "idempotencyConflict": {
                    "value": {
                      "error": "IDEMPOTENCY_CONFLICT",
                      "message": "Idempotency-Key was reused with a different request body"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "headers": {
              "X-Request-Id": {
                "description": "Correlation id for this response. Echoes inbound X-Request-Id when provided.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              },
              "Retry-After": {
                "description": "Seconds until the client should retry (UTC midnight for daily caps).",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RATE_LIMITED",
                  "message": "rate limit exceeded"
                }
              }
            }
          },
          "503": {
            "description": "Temporary shortener unavailability (INTERNAL_ERROR)",
            "headers": {
              "X-Request-Id": {
                "description": "Correlation id for this response. Echoes inbound X-Request-Id when provided.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "INTERNAL_ERROR",
                  "message": "shortener unavailable"
                }
              }
            }
          }
        }
      }
    },
    "/v1/links/{id}": {
      "get": {
        "operationId": "getLinkStatus",
        "summary": "Get link status",
        "description": "Public status lookup by short code. No authentication required. Destination is omitted while the link is quarantined.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Short code returned as `id` from POST /v1/links",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$",
              "maxLength": 32
            }
          },
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "description": "Optional client correlation id. Server generates one when omitted and always returns it.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current link state",
            "headers": {
              "X-Request-Id": {
                "description": "Correlation id for this response. Echoes inbound X-Request-Id when provided.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkStatusResponse"
                }
              }
            }
          },
          "404": {
            "description": "Unknown short code",
            "headers": {
              "X-Request-Id": {
                "description": "Correlation id for this response. Echoes inbound X-Request-Id when provided.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "duan_session",
        "description": "Optional HMAC-sealed session cookie from POST /api/auth/signin. Anonymous create is allowed; this cookie is not required for POST /v1/links."
      }
    },
    "schemas": {
      "CreateLinkRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "Destination URL (preferred). HTTP or HTTPS only."
          },
          "longUrl": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "Alias of url for /api/links compatibility."
          },
          "alias": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$",
            "minLength": 1,
            "maxLength": 32,
            "description": "Optional custom short code. ASCII letters, digits, underscore or hyphen. Must not be reserved."
          },
          "customSlug": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$",
            "maxLength": 32,
            "description": "Alias of alias for compatibility."
          },
          "source": {
            "type": "string",
            "maxLength": 64,
            "description": "Caller hint (e.g. agent). Client hint only — not trusted click attribution.",
            "example": "agent"
          },
          "title": {
            "type": "string",
            "maxLength": 256
          },
          "locale": {
            "type": "string",
            "enum": [
              "en",
              "zh-CN"
            ],
            "description": "UI/event locale. zh-cn and zh are accepted as zh-CN."
          }
        },
        "anyOf": [
          {
            "required": [
              "url"
            ]
          },
          {
            "required": [
              "longUrl"
            ]
          }
        ]
      },
      "CreateLinkResponse": {
        "type": "object",
        "required": [
          "id",
          "short_url",
          "destination",
          "qr_url",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Short code"
          },
          "short_url": {
            "type": "string",
            "format": "uri",
            "description": "Public short URL (Unicode apex when configured)",
            "example": "https://短.在线/A7x"
          },
          "destination": {
            "type": "string",
            "format": "uri"
          },
          "qr_url": {
            "type": "string",
            "format": "uri",
            "description": "Short URL tagged for QR attribution (?s=qr)"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "state": {
            "type": "string",
            "enum": [
              "active",
              "quarantined"
            ]
          },
          "status_url": {
            "type": "string",
            "description": "Relative URL for GET status, e.g. /v1/links/A7x"
          },
          "request_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "CreateLinkQuarantinedResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CreateLinkResponse"
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "const": "LINK_QUARANTINED"
              },
              "message": {
                "type": "string"
              }
            }
          }
        ]
      },
      "LinkStatusResponse": {
        "type": "object",
        "required": [
          "id",
          "state",
          "created_at",
          "status_url"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "enum": [
              "active",
              "quarantined"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "status_url": {
            "type": "string"
          },
          "short_url": {
            "type": "string",
            "format": "uri",
            "description": "Omitted while quarantined"
          },
          "destination": {
            "type": "string",
            "format": "uri",
            "description": "Omitted while quarantined"
          },
          "request_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "INVALID_URL",
              "BLOCKED_DESTINATION",
              "RATE_LIMITED",
              "ALIAS_TAKEN",
              "RESERVED_ALIAS",
              "AUTH_REQUIRED",
              "LINK_QUARANTINED",
              "IDEMPOTENCY_KEY_REQUIRED",
              "IDEMPOTENCY_CONFLICT",
              "INTERNAL_ERROR",
              "VALIDATION_ERROR"
            ]
          },
          "message": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "format": "uri",
            "description": "RFC 9457 type URI"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "code": {
            "type": "string",
            "enum": [
              "INVALID_URL",
              "BLOCKED_DESTINATION",
              "RATE_LIMITED",
              "ALIAS_TAKEN",
              "RESERVED_ALIAS",
              "AUTH_REQUIRED",
              "LINK_QUARANTINED",
              "IDEMPOTENCY_KEY_REQUIRED",
              "IDEMPOTENCY_CONFLICT",
              "INTERNAL_ERROR",
              "VALIDATION_ERROR"
            ],
            "description": "Same as error; RFC 9457 extension"
          },
          "detail": {
            "type": "string"
          },
          "request_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      }
    }
  }
}
