{
  "openapi": "3.1.0",
  "info": {
    "title": "佐印 zuo.in Short Link API",
    "version": "1.0.0",
    "description": "Bitly v4-compatible subset for server-side and AI coding agent integrations. All API operations require a zuo.in owner Bearer token. Provision at /dashboard/api after owner Google login; ordinary visitors cannot create tokens. Store ZUOIN_TOKEN only in server-side secrets. One active token per owner; rotation/revocation invalidates old tokens. Accept 200 and 201 for shortening; leave force_new_link false for retries. Read https://zuo.in/docs. No PATCH/DELETE, custom slugs, arbitrary domains, campaigns, webhooks, Bitly OAuth or MCP API. Unsupported routes and reverse-proxy failures may return non-JSON responses."
  },
  "servers": [
    {
      "url": "https://zuo.in"
    }
  ],
  "externalDocs": {
    "url": "https://zuo.in/docs",
    "description": "Human-readable API documentation and coding agent instructions"
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Links"
    },
    {
      "name": "Analytics"
    },
    {
      "name": "Account"
    }
  ],
  "paths": {
    "/v4/shorten": {
      "post": {
        "operationId": "shortenLink",
        "tags": [
          "Links"
        ],
        "summary": "Create or reuse a short link",
        "responses": {
          "200": {
            "description": "Existing link reused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Bitlink"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "422": {
            "$ref": "#/components/responses/422"
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "201": {
            "description": "New short link created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Bitlink"
                }
              }
            }
          }
        },
        "description": "201 means created; 200 means an existing unexpired link was reused. Use response.link for sharing, response.id for subsequent API calls. Use a client timeout of at least 30 seconds. Custom slugs and expiration are not supported by this API.",
        "requestBody": {
          "required": true,
          "description": "UTF-8 JSON; maximum body size 64 KiB.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ShortenRequest"
              }
            }
          }
        }
      }
    },
    "/v4/bitlinks": {
      "post": {
        "operationId": "createBitlink",
        "tags": [
          "Links"
        ],
        "summary": "Create or reuse a short link",
        "responses": {
          "200": {
            "description": "Existing link reused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Bitlink"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "422": {
            "$ref": "#/components/responses/422"
          },
          "500": {
            "$ref": "#/components/responses/500"
          },
          "201": {
            "description": "New short link created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Bitlink"
                }
              }
            }
          }
        },
        "description": "201 means created; 200 means an existing unexpired link was reused. Use response.link for sharing, response.id for subsequent API calls. Use a client timeout of at least 30 seconds. Custom slugs and expiration are not supported by this API.",
        "requestBody": {
          "required": true,
          "description": "UTF-8 JSON; maximum body size 64 KiB.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ShortenRequest"
              }
            }
          }
        }
      }
    },
    "/v4/expand": {
      "post": {
        "operationId": "expandLink",
        "tags": [
          "Links"
        ],
        "summary": "Expand a link owned by this token owner",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExpandedBitlink"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "requestBody": {
          "required": true,
          "description": "UTF-8 JSON; maximum body size 64 KiB.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExpandRequest"
              }
            }
          }
        }
      }
    },
    "/v4/bitlinks/{domain}/{code}": {
      "get": {
        "operationId": "getBitlink",
        "tags": [
          "Links"
        ],
        "summary": "Get a link owned by this token owner",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Bitlink"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "description": "The zuo.in short-link host.",
            "schema": {
              "type": "string",
              "enum": [
                "zuo.in"
              ]
            }
          },
          {
            "name": "code",
            "in": "path",
            "required": true,
            "description": "Code from response.id; case-sensitive. Do not assume a fixed length or character set for existing links.",
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ]
      }
    },
    "/v4/bitlinks/{domain}/{code}/clicks": {
      "get": {
        "operationId": "getLinkClicks",
        "tags": [
          "Analytics"
        ],
        "summary": "Get click buckets, newest first, including zero buckets",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClickSeries"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "description": "Analytics are recorded asynchronously and may lag. Counts include bots/preview requests; they are not unique human visitor counts.",
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "description": "The zuo.in short-link host.",
            "schema": {
              "type": "string",
              "enum": [
                "zuo.in"
              ]
            }
          },
          {
            "name": "code",
            "in": "path",
            "required": true,
            "description": "Code from response.id; case-sensitive. Do not assume a fixed length or character set for existing links.",
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "unit",
            "in": "query",
            "required": false,
            "description": "UTC calendar unit; the current bucket is included. Weeks start Sunday.",
            "schema": {
              "type": "string",
              "enum": [
                "minute",
                "hour",
                "day",
                "week",
                "month"
              ],
              "default": "day"
            }
          },
          {
            "name": "units",
            "in": "query",
            "required": false,
            "description": "Number of calendar buckets, including the current bucket.",
            "schema": {
              "type": "integer",
              "default": 7,
              "minimum": 1,
              "maximum": 366
            }
          },
          {
            "name": "unit_reference",
            "in": "query",
            "required": false,
            "description": "RFC3339 timestamp selecting the reference bucket; defaults to the current time and is normalized to UTC.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "timezone",
            "in": "query",
            "required": false,
            "description": "Omit or use 0. Only UTC is supported, unlike the Taipei-time website reports.",
            "schema": {
              "type": "string",
              "enum": [
                "0"
              ]
            }
          }
        ]
      }
    },
    "/v4/bitlinks/{domain}/{code}/clicks/summary": {
      "get": {
        "operationId": "getLinkClickSummary",
        "tags": [
          "Analytics"
        ],
        "summary": "Get total clicks",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClickSummary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400"
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "404": {
            "$ref": "#/components/responses/404"
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "description": "Analytics are recorded asynchronously and may lag. Counts include bots/preview requests; they are not unique human visitor counts.",
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "description": "The zuo.in short-link host.",
            "schema": {
              "type": "string",
              "enum": [
                "zuo.in"
              ]
            }
          },
          {
            "name": "code",
            "in": "path",
            "required": true,
            "description": "Code from response.id; case-sensitive. Do not assume a fixed length or character set for existing links.",
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "unit",
            "in": "query",
            "required": false,
            "description": "UTC calendar unit; the current bucket is included. Weeks start Sunday.",
            "schema": {
              "type": "string",
              "enum": [
                "minute",
                "hour",
                "day",
                "week",
                "month"
              ],
              "default": "day"
            }
          },
          {
            "name": "units",
            "in": "query",
            "required": false,
            "description": "Number of calendar buckets, including the current bucket. -1 returns the lifetime total.",
            "schema": {
              "type": "integer",
              "default": 7,
              "oneOf": [
                {
                  "minimum": 1,
                  "maximum": 366
                },
                {
                  "const": -1
                }
              ]
            }
          },
          {
            "name": "unit_reference",
            "in": "query",
            "required": false,
            "description": "RFC3339 timestamp selecting the reference bucket; defaults to the current time and is normalized to UTC.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "timezone",
            "in": "query",
            "required": false,
            "description": "Omit or use 0. Only UTC is supported, unlike the Taipei-time website reports.",
            "schema": {
              "type": "string",
              "enum": [
                "0"
              ]
            }
          }
        ]
      }
    },
    "/v4/user": {
      "get": {
        "operationId": "getCurrentUser",
        "tags": [
          "Account"
        ],
        "summary": "Validate token and get the current owner profile",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          }
        }
      }
    },
    "/v4/organizations": {
      "get": {
        "operationId": "listOrganizations",
        "tags": [
          "Account"
        ],
        "summary": "List the single supported organization",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "organizations": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Organization"
                      }
                    }
                  },
                  "required": [
                    "organizations"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          }
        }
      }
    },
    "/v4/groups": {
      "get": {
        "operationId": "listGroups",
        "tags": [
          "Account"
        ],
        "summary": "List the single supported group",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "groups": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Group"
                      }
                    }
                  },
                  "required": [
                    "groups"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "404": {
            "$ref": "#/components/responses/404"
          }
        },
        "parameters": [
          {
            "name": "organization_guid",
            "in": "query",
            "required": false,
            "description": "Optional organization filter.",
            "schema": {
              "type": "string",
              "enum": [
                "ZuolaOrganization"
              ]
            }
          }
        ]
      }
    },
    "/v4/groups/{guid}": {
      "get": {
        "operationId": "getGroup",
        "tags": [
          "Account"
        ],
        "summary": "Get group details",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Group"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "404": {
            "$ref": "#/components/responses/404"
          }
        },
        "parameters": [
          {
            "name": "guid",
            "in": "path",
            "required": true,
            "description": "The only supported group GUID.",
            "schema": {
              "type": "string",
              "enum": [
                "ZuoinGroup"
              ]
            }
          }
        ]
      }
    },
    "/v4/bsds": {
      "get": {
        "operationId": "listShortDomains",
        "tags": [
          "Account"
        ],
        "summary": "List available short-link domains",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "bsds": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "zuo.in"
                        ]
                      }
                    }
                  },
                  "required": [
                    "bsds"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "zui_<secret>",
        "description": "zuo.in owner token, not a Bitly token. Obtain at https://zuo.in/dashboard/api. Never put it in client-side code, URLs or logs."
      }
    },
    "schemas": {
      "APIError": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "resource": {
            "type": "string"
          }
        },
        "required": [
          "message",
          "description",
          "resource"
        ]
      },
      "ShortenRequest": {
        "type": "object",
        "properties": {
          "long_url": {
            "type": "string",
            "format": "uri",
            "maxLength": 8192,
            "description": "Required HTTP(S) destination. The actual limit is 8192 UTF-8 bytes; maxLength is an upper bound. The service fetches it with a 15-second timeout and a 10-redirect limit, requires final HTTP 200 and stores the final URL.",
            "example": "https://example.com/"
          },
          "domain": {
            "type": "string",
            "enum": [
              "zuo.in"
            ],
            "description": "Optional; only zuo.in is supported (case-insensitive)."
          },
          "group_guid": {
            "type": "string",
            "enum": [
              "ZuoinGroup"
            ]
          },
          "title": {
            "type": "string",
            "description": "Optional title; reusing an existing link does not update its title."
          },
          "force_new_link": {
            "type": "boolean",
            "default": false,
            "description": "False/omitted reuses an unexpired link belonging to this owner with the same resolved destination. True creates a new link on every call, including retries. No Idempotency-Key contract is provided."
          },
          "tags": {
            "type": "array",
            "maxItems": 0,
            "items": {
              "type": "string"
            },
            "description": "Only an empty array is supported; nonempty arrays return 422."
          },
          "deeplinks": {
            "type": "array",
            "maxItems": 0,
            "items": {},
            "description": "Only an empty array is supported; nonempty arrays return 422."
          }
        },
        "required": [
          "long_url"
        ]
      },
      "Bitlink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "zuo.in/7Ab",
            "description": "Use this host/code identity for lookup and click paths; preserve the slash."
          },
          "link": {
            "type": "string",
            "format": "uri",
            "example": "https://zuo.in/7Ab"
          },
          "long_url": {
            "type": "string",
            "format": "uri",
            "example": "https://example.com/"
          },
          "title": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by": {
            "type": "string",
            "enum": [
              "zuola"
            ]
          },
          "archived": {
            "type": "boolean",
            "const": false
          },
          "tags": {
            "type": "array",
            "maxItems": 0,
            "items": {
              "type": "string"
            }
          },
          "custom_bitlinks": {
            "type": "array",
            "maxItems": 0,
            "items": {
              "type": "string"
            }
          },
          "deeplinks": {
            "type": "array",
            "maxItems": 0,
            "items": {}
          },
          "references": {
            "type": "object",
            "properties": {
              "group": {
                "type": "string",
                "format": "uri",
                "example": "https://zuo.in/v4/groups/ZuoinGroup"
              }
            },
            "required": [
              "group"
            ]
          }
        },
        "required": [
          "id",
          "link",
          "long_url",
          "title",
          "created_at",
          "created_by",
          "archived",
          "tags",
          "custom_bitlinks",
          "deeplinks",
          "references"
        ]
      },
      "ExpandRequest": {
        "type": "object",
        "properties": {
          "bitlink_id": {
            "type": "string",
            "description": "A zuo.in/code ID or a full http(s) short URL. No query, fragment or userinfo. Must belong to the current token owner.",
            "example": "zuo.in/7Ab"
          }
        },
        "required": [
          "bitlink_id"
        ]
      },
      "ExpandedBitlink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "zuo.in/7Ab",
            "description": "Use this host/code identity for lookup and click paths; preserve the slash."
          },
          "link": {
            "type": "string",
            "format": "uri",
            "example": "https://zuo.in/7Ab"
          },
          "long_url": {
            "type": "string",
            "format": "uri",
            "example": "https://example.com/"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "link",
          "long_url",
          "created_at"
        ]
      },
      "ClickBucket": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date-time"
          },
          "clicks": {
            "type": "integer",
            "format": "int64",
            "minimum": 0
          }
        },
        "required": [
          "date",
          "clicks"
        ]
      },
      "ClickSeries": {
        "type": "object",
        "properties": {
          "unit": {
            "type": "string",
            "enum": [
              "minute",
              "hour",
              "day",
              "week",
              "month"
            ]
          },
          "units": {
            "type": "integer"
          },
          "unit_reference": {
            "type": "string",
            "format": "date-time"
          },
          "link_clicks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClickBucket"
            }
          }
        },
        "required": [
          "unit",
          "units",
          "unit_reference",
          "link_clicks"
        ]
      },
      "ClickSummary": {
        "type": "object",
        "properties": {
          "unit": {
            "type": "string",
            "enum": [
              "minute",
              "hour",
              "day",
              "week",
              "month"
            ]
          },
          "units": {
            "type": "integer"
          },
          "unit_reference": {
            "type": "string",
            "format": "date-time"
          },
          "total_clicks": {
            "type": "integer",
            "format": "int64",
            "minimum": 0
          }
        },
        "required": [
          "unit",
          "units",
          "unit_reference",
          "total_clicks"
        ]
      },
      "User": {
        "type": "object",
        "properties": {
          "login": {
            "type": "string",
            "enum": [
              "zuola"
            ]
          },
          "name": {
            "type": "string"
          },
          "is_active": {
            "type": "boolean"
          },
          "is_2fa_enabled": {
            "type": "boolean"
          },
          "default_group_guid": {
            "type": "string",
            "enum": [
              "ZuoinGroup"
            ]
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "modified": {
            "type": "string",
            "format": "date-time"
          },
          "emails": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "email": {
                  "type": "string",
                  "format": "email"
                },
                "is_primary": {
                  "type": "boolean"
                },
                "is_verified": {
                  "type": "boolean"
                }
              },
              "required": [
                "email",
                "is_primary",
                "is_verified"
              ]
            }
          }
        },
        "required": [
          "login",
          "name",
          "is_active",
          "is_2fa_enabled",
          "default_group_guid",
          "created",
          "modified",
          "emails"
        ]
      },
      "Organization": {
        "type": "object",
        "properties": {
          "guid": {
            "type": "string",
            "enum": [
              "ZuolaOrganization"
            ]
          },
          "name": {
            "type": "string",
            "enum": [
              "Zuola"
            ]
          },
          "is_active": {
            "type": "boolean"
          }
        },
        "required": [
          "guid",
          "name",
          "is_active"
        ]
      },
      "Group": {
        "type": "object",
        "properties": {
          "guid": {
            "type": "string",
            "enum": [
              "ZuoinGroup"
            ]
          },
          "name": {
            "type": "string"
          },
          "organization_guid": {
            "type": "string",
            "enum": [
              "ZuolaOrganization"
            ]
          },
          "bsds": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "zuo.in"
              ]
            }
          },
          "is_active": {
            "type": "boolean"
          },
          "role": {
            "type": "string",
            "enum": [
              "org-admin"
            ]
          }
        },
        "required": [
          "guid",
          "name",
          "organization_guid",
          "bsds",
          "is_active",
          "role"
        ]
      }
    },
    "responses": {
      "400": {
        "description": "Invalid JSON, argument or body exceeding 64 KiB. message is INVALID_ARG_*.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/APIError"
            }
          }
        }
      },
      "401": {
        "description": "Missing, invalid or revoked zuo.in token, or token not belonging to the site owner. message is UNAUTHORIZED.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/APIError"
            }
          }
        }
      },
      "404": {
        "description": "Unknown resource/group/organization or link not belonging to the token owner. message is NOT_FOUND.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/APIError"
            }
          }
        }
      },
      "422": {
        "description": "Destination did not resolve to HTTP 200 within the validation limits (INVALID_ARG_LONG_URL), or nonempty tags/deeplinks (UNSUPPORTED_OPERATION).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/APIError"
            }
          }
        }
      },
      "500": {
        "description": "Storage or query failure. message is INTERNAL_ERROR.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/APIError"
            }
          }
        }
      }
    }
  }
}
