{
  "openapi": "3.1.0",
  "info": {
    "title": "TinyTo API",
    "version": "1.0.0",
    "summary": "Create short links, read their stats and manage them programmatically.",
    "description": "REST API for TinyTo. Requires the Advanced plan. Authenticate with `Authorization: Bearer <key>` or `X-API-Key`.",
    "license": {
      "name": "Proprietary — use is governed by the TinyTo terms",
      "url": "https://tinyto.me/terms"
    }
  },
  "servers": [
    {
      "url": "https://tinyto.me/api",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Links",
      "description": "Create, list, read and delete short links."
    },
    {
      "name": "Account",
      "description": "Identify the key and read plan usage."
    }
  ],
  "paths": {
    "/v1/me": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getAccount",
        "summary": "Account and key",
        "description": "Confirms the key works and returns the plan, link usage and key scope.",
        "responses": {
          "200": {
            "description": "Account and key information",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Read-only key on a write call, or plan without API access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "More than 120 requests in a minute",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/links": {
      "get": {
        "tags": [
          "Links"
        ],
        "operationId": "listLinks",
        "summary": "List links",
        "description": "Returns the account's links, newest first.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Page size, 1-100. Default 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "How many links to skip.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of links",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkList"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Read-only key on a write call, or plan without API access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "More than 120 requests in a minute",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Links"
        ],
        "operationId": "createLink",
        "summary": "Create a link",
        "description": "Creates a short link owned by the key's account. Counts against the plan's active-link allowance.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateLinkInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Link"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, slug or expiry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The account has no active links left on its plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Read-only key on a write call, or plan without API access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "More than 120 requests in a minute",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/links/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The link id returned at creation, not the slug.",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Links"
        ],
        "operationId": "getLink",
        "summary": "Get a link",
        "responses": {
          "200": {
            "description": "The link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkDetail"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Read-only key on a write call, or plan without API access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The link does not exist or belongs to another account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "More than 120 requests in a minute",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Links"
        ],
        "operationId": "deleteLink",
        "summary": "Delete a link",
        "description": "Deletes the link and its stats. The short URL stops resolving immediately.",
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Read-only key on a write call, or plan without API access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The link does not exist or belongs to another account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "More than 120 requests in a minute",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/links/{id}/stats": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The link id.",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Links"
        ],
        "operationId": "getLinkStats",
        "summary": "Link stats",
        "description": "Click totals, top countries and the hour/weekday heatmap. Bot traffic is excluded from the heatmap.",
        "responses": {
          "200": {
            "description": "Stats for one link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkStats"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Read-only key on a write call, or plan without API access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The link does not exist or belongs to another account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "More than 120 requests in a minute",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key created in Dashboard → API. Format: `tto_live_` + 43 base64url characters."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "data",
          "error"
        ],
        "properties": {
          "data": {
            "type": "null",
            "description": "Always null on failure."
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "unauthorized",
                  "forbidden",
                  "not_found",
                  "plan_limit_reached",
                  "rate_limited",
                  "server_error"
                ]
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "CreateLinkInput": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "Destination. `http`/`https` is assumed if the scheme is missing."
          },
          "slug": {
            "type": "string",
            "pattern": "^[a-zA-Z0-9]{3,50}$",
            "description": "Custom path. Must be free across the whole service."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp in the future."
          }
        }
      },
      "Link": {
        "type": "object",
        "required": [
          "id",
          "short_url",
          "stats_url",
          "url",
          "slug",
          "expires_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "short_url": {
            "type": "string",
            "format": "uri"
          },
          "stats_url": {
            "type": "string",
            "format": "uri"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "slug": {
            "type": "string"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "LinkDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "short_url": {
            "type": "string",
            "format": "uri"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "slug": {
            "type": "string"
          },
          "clicks": {
            "type": "integer",
            "minimum": 0
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "password_protected": {
            "type": "boolean"
          }
        }
      },
      "LinkListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "short_url": {
            "type": "string",
            "format": "uri"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "clicks": {
            "type": "integer",
            "minimum": 0
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "LinkList": {
        "type": "object",
        "required": [
          "items",
          "pagination"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LinkListItem"
            }
          },
          "pagination": {
            "type": "object",
            "required": [
              "total",
              "limit",
              "offset",
              "has_more"
            ],
            "properties": {
              "total": {
                "type": "integer"
              },
              "limit": {
                "type": "integer"
              },
              "offset": {
                "type": "integer"
              },
              "has_more": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "DeleteResult": {
        "type": "object",
        "required": [
          "deleted",
          "id"
        ],
        "properties": {
          "deleted": {
            "type": "boolean",
            "const": true
          },
          "id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "CountryClicks": {
        "type": "object",
        "required": [
          "country",
          "clicks"
        ],
        "properties": {
          "country": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 code."
          },
          "clicks": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "HeatmapCell": {
        "type": "object",
        "required": [
          "hour",
          "dayOfWeek",
          "clicks"
        ],
        "properties": {
          "hour": {
            "type": "integer",
            "minimum": 0,
            "maximum": 23
          },
          "dayOfWeek": {
            "type": "integer",
            "minimum": 0,
            "maximum": 6,
            "description": "0 = Sunday."
          },
          "clicks": {
            "type": "integer",
            "minimum": 1
          }
        }
      },
      "LinkStats": {
        "type": "object",
        "required": [
          "id",
          "short_url",
          "total_clicks",
          "countries",
          "heatmap"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "short_url": {
            "type": "string",
            "format": "uri"
          },
          "total_clicks": {
            "type": "integer",
            "minimum": 0
          },
          "countries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CountryClicks"
            }
          },
          "heatmap": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HeatmapCell"
            }
          },
          "heatmap_shape": {
            "type": "string"
          }
        }
      },
      "Account": {
        "type": "object",
        "required": [
          "plan",
          "link_usage",
          "key_scope"
        ],
        "properties": {
          "plan": {
            "type": "string",
            "enum": [
              "advanced"
            ]
          },
          "link_usage": {
            "type": "object",
            "required": [
              "used",
              "limit",
              "unlimited"
            ],
            "properties": {
              "used": {
                "type": "integer",
                "minimum": 0
              },
              "limit": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "unlimited": {
                "type": "boolean"
              }
            }
          },
          "key_scope": {
            "type": "string",
            "enum": [
              "read",
              "write"
            ]
          }
        }
      }
    }
  }
}