{
  "openapi": "3.1.0",
  "info": {
    "title": "Compress Me API",
    "version": "1.0.0",
    "summary": "Programmatic short-link creation for Compress Me.",
    "description": "The Compress Me API lets Business-plan accounts create short links programmatically. It runs the identical validation, safety, and slug-allocation pipeline as the web app, so links created via the API behave exactly like links created in the dashboard.\n\nAll requests are authenticated with a bearer API key, all responses are JSON, and all errors share a consistent shape (`ok`, `error`, `code`).",
    "termsOfService": "https://comprs.me/terms",
    "contact": {
      "name": "Compress Me Support",
      "url": "https://comprs.me/contact"
    }
  },
  "servers": [
    {
      "url": "https://comprs.me",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Links",
      "description": "Create short links."
    }
  ],
  "paths": {
    "/v1/links": {
      "get": {
        "tags": ["Links"],
        "summary": "List links",
        "operationId": "listLinks",
        "description": "Returns the authenticated account's links, newest first. Requires a `read` scope (a `read_write` key also works).",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of links to return.",
            "schema": {
              "type": "integer",
              "default": 25,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of links to skip, for pagination.",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of links, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkList"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed. Error codes: `missing_auth`, `invalid_key`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not permitted. Error codes: `account_inactive`, `api_not_in_plan`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; see `Retry-After`. Error code: `rate_limited`.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": ["Links"],
        "summary": "Create a short link",
        "operationId": "createLink",
        "description": "Creates a short link for the authenticated account. Requires a key with the `read_write` scope. The destination URL is run through the same safety gauntlet as the web app (format validation, malicious-URL filtering, and rate limiting) before a slug is allocated.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateLinkRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "Minimal — random slug, no expiry",
                  "value": {
                    "url": "https://example.com/a/very/long/path?with=params"
                  }
                },
                "full": {
                  "summary": "Custom slug with a 30-day expiry",
                  "value": {
                    "url": "https://example.com/spring-campaign",
                    "custom_slug": "spring26",
                    "expiry": "30d"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Link created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateLinkResponse"
                },
                "example": {
                  "ok": true,
                  "slug": "aB3xZ9",
                  "short_url": "https://comprs.me/aB3xZ9",
                  "qr_png": "https://comprs.me/qr/aB3xZ9.png",
                  "qr_svg": "https://comprs.me/qr/aB3xZ9.svg",
                  "expires_at": "2026-07-05 14:30:00"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request body (not a JSON object). Error code: `invalid_body`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "Request body must be a JSON object with at least a \"url\" field.",
                  "code": "invalid_body"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed. Error codes: `missing_auth` (no key sent), `invalid_key` (unknown, revoked, or expired key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "Invalid, revoked, or expired API key.",
                  "code": "invalid_key"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated but not permitted. Error codes: `account_inactive`, `api_not_in_plan` (API access requires the Business plan), `insufficient_scope` (a read-only key was used for a write).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "This key is read-only; a read_write key is required.",
                  "code": "insufficient_scope"
                }
              }
            }
          },
          "409": {
            "description": "The requested `custom_slug` is already taken. Error code: `slug_taken`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "That short link was just taken by someone else. Please pick another.",
                  "code": "slug_taken"
                }
              }
            }
          },
          "422": {
            "description": "The request was well-formed but rejected. Error codes: `invalid_url` (not a valid http/https URL), `unsafe_url` (flagged by malicious-URL filtering), `slug_invalid` (custom slug failed validation — the `error` field explains why).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "This URL has been flagged as unsafe and cannot be shortened.",
                  "code": "unsafe_url"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Error code: `rate_limited`. A `Retry-After` header (seconds) indicates when to retry. Limits: 60 requests/minute per account, plus a daily plan cap; repeated auth failures are throttled per IP.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "Per-minute API rate limit reached (60/min).",
                  "code": "rate_limited"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. Only POST is supported on /v1/links in v1. Error code: `method_not_allowed`.",
            "headers": {
              "Allow": {
                "description": "Supported methods.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "Only POST is supported on /v1/links in v1.",
                  "code": "method_not_allowed"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error. Error code: `server_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "An unexpected error occurred.",
                  "code": "server_error"
                }
              }
            }
          },
          "503": {
            "description": "Could not allocate a unique slug after retries; transient. Error code: `allocation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "ok": false,
                  "error": "Could not allocate a unique short code. Please retry.",
                  "code": "allocation_failed"
                }
              }
            }
          }
        }
      }
    },
    "/v1/links/{slug}": {
      "parameters": [
        {
          "name": "slug",
          "in": "path",
          "required": true,
          "description": "The short code of the link.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": ["Links"],
        "summary": "Get a link",
        "operationId": "getLink",
        "description": "Fetches a single link owned by the authenticated account. Requires a `read` scope.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The link.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "type": "object",
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "const": true
                        }
                      }
                    },
                    {
                      "$ref": "#/components/schemas/Link"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not permitted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No link with that slug exists for this account — returned whether the slug is unknown or owned by another account (existence is never leaked across accounts). Error code: `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; see `Retry-After`.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": ["Links"],
        "summary": "Delete a link",
        "operationId": "deleteLink",
        "description": "Permanently deletes a link owned by the authenticated account, along with its click history. Requires a `read_write` scope. This cannot be undone.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The link was deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["ok", "deleted", "slug"],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    },
                    "slug": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "deleted": true,
                  "slug": "aB3xZ9"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not permitted. A read-only key returns `insufficient_scope`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No link with that slug exists for this account. Error code: `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; see `Retry-After`.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "cmpr_live_<key>",
        "description": "Send your API key as a bearer token: `Authorization: Bearer cmpr_live_<key>`. Keys are created and revoked from the dashboard under Settings (Business plan only) and are shown once at creation. Keys carry a scope of `read` or `read_write`; creating links requires `read_write`."
      }
    },
    "schemas": {
      "CreateLinkRequest": {
        "type": "object",
        "required": ["url"],
        "additionalProperties": false,
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The destination URL. If you omit the scheme, `https://` is prepended automatically.",
            "examples": ["https://example.com/a/very/long/path"]
          },
          "custom_slug": {
            "type": "string",
            "description": "Optional custom short code (3–20 letters and numbers). Must be available and pass validation; some short or premium slugs may require a higher tier. If omitted, a random slug is generated. The alias `slug` is also accepted.",
            "examples": ["spring26"]
          },
          "slug": {
            "type": "string",
            "deprecated": true,
            "description": "Alias for `custom_slug`. Prefer `custom_slug`."
          },
          "expiry": {
            "type": "string",
            "enum": ["never", "24h", "7d", "30d"],
            "default": "never",
            "description": "How long the link stays active. Any value other than these four is treated as `never`."
          }
        }
      },
      "CreateLinkResponse": {
        "type": "object",
        "required": ["ok", "slug", "short_url", "qr_png", "qr_svg", "expires_at"],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "slug": {
            "type": "string",
            "description": "The allocated short code.",
            "examples": ["aB3xZ9"]
          },
          "short_url": {
            "type": "string",
            "format": "uri",
            "description": "The full short link.",
            "examples": ["https://comprs.me/aB3xZ9"]
          },
          "qr_png": {
            "type": "string",
            "format": "uri",
            "description": "URL of a PNG QR code for the short link.",
            "examples": ["https://comprs.me/qr/aB3xZ9.png"]
          },
          "qr_svg": {
            "type": "string",
            "format": "uri",
            "description": "URL of an SVG QR code for the short link.",
            "examples": ["https://comprs.me/qr/aB3xZ9.svg"]
          },
          "expires_at": {
            "type": ["string", "null"],
            "description": "Expiry timestamp in `YYYY-MM-DD HH:MM:SS` (server time), or null when the link never expires.",
            "examples": ["2026-07-05 14:30:00", null]
          }
        }
      },
      "Link": {
        "type": "object",
        "description": "Full representation of a link.",
        "properties": {
          "slug": {
            "type": "string",
            "examples": ["aB3xZ9"]
          },
          "short_url": {
            "type": "string",
            "format": "uri",
            "examples": ["https://comprs.me/aB3xZ9"]
          },
          "long_url": {
            "type": "string",
            "format": "uri",
            "description": "The destination URL."
          },
          "qr_png": {
            "type": "string",
            "format": "uri"
          },
          "qr_svg": {
            "type": "string",
            "format": "uri"
          },
          "clicks": {
            "type": "integer",
            "description": "Total recorded clicks."
          },
          "last_clicked_at": {
            "type": ["string", "null"],
            "description": "Timestamp of the most recent click (server time), or null."
          },
          "expires_at": {
            "type": ["string", "null"],
            "description": "Expiry timestamp (YYYY-MM-DD HH:MM:SS, server time), or null when the link never expires."
          },
          "created_at": {
            "type": "string",
            "description": "Creation timestamp (YYYY-MM-DD HH:MM:SS, server time)."
          },
          "is_custom": {
            "type": "boolean",
            "description": "Whether the slug was user-chosen rather than generated."
          },
          "disabled": {
            "type": "boolean",
            "description": "Whether the link is currently disabled (stops redirecting)."
          },
          "flagged": {
            "type": "boolean",
            "description": "Whether the link is flagged for review."
          }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer",
            "description": "Total number of links on the account."
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        }
      },
      "LinkList": {
        "type": "object",
        "required": ["ok", "data", "pagination"],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Link"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": ["ok", "error", "code"],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string",
            "description": "Human-readable explanation, safe to surface to end users."
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable error code. Branch on this, not on the message.",
            "enum": [
              "invalid_body",
              "missing_auth",
              "invalid_key",
              "account_inactive",
              "api_not_in_plan",
              "insufficient_scope",
              "invalid_url",
              "unsafe_url",
              "slug_invalid",
              "slug_taken",
              "rate_limited",
              "allocation_failed",
              "method_not_allowed",
              "not_found",
              "server_error"
            ]
          }
        }
      }
    }
  }
}
