{
  "openapi": "3.1.0",
  "info": {
    "title": "INBIO API",
    "version": "1.0.0",
    "description": "URL shortener with click analytics and customizable QR codes. Two free keyless endpoints (POST /api/shorten, GET /api/qr) plus a full token-authenticated REST API. MCP server: https://mcp.in.bio/mcp",
    "contact": { "email": "hello@in.bio", "url": "https://in.bio/contact" },
    "termsOfService": "https://in.bio/terms"
  },
  "servers": [{ "url": "https://in.bio" }],
  "externalDocs": { "url": "https://docs.in.bio/api", "description": "Full API documentation" },
  "security": [{ "bearerAuth": [] }],
  "paths": {
    "/api/shorten": {
      "post": {
        "operationId": "shortenUrl",
        "summary": "Shorten a URL (free, no auth)",
        "description": "No account or API key required. Rate limit: 5/min per IP (100/day). Anonymous links are deleted after 30 days unless claimed via claim_url.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["url"], "properties": { "url": { "type": "string", "format": "uri", "maxLength": 2048 } } } } }
        },
        "responses": {
          "201": { "description": "Link created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ShortenResult" } } } },
          "422": { "description": "URL rejected", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": { "type": "string" } } } } } },
          "429": { "description": "Rate limit exceeded" }
        }
      }
    },
    "/api/qr": {
      "get": {
        "operationId": "generateQr",
        "summary": "Generate a styled QR code (free, no auth)",
        "description": "Returns the image directly (PNG default, SVG with format=svg). Rate limit: 30/min per IP, cached one day. Payload fields depend on type: url→url; text→text; email→to,subject,body; phone→phone; sms→phone,body; whatsapp→phone,text; wifi→ssid,password,encryption; vcard→name,org,title,phone,email,url.",
        "security": [],
        "parameters": [
          { "name": "type", "in": "query", "schema": { "type": "string", "enum": ["url", "text", "email", "phone", "sms", "whatsapp", "wifi", "vcard"], "default": "url" } },
          { "name": "url", "in": "query", "schema": { "type": "string" }, "description": "Required when type=url" },
          { "name": "format", "in": "query", "schema": { "type": "string", "enum": ["png", "svg"], "default": "png" } },
          { "name": "size", "in": "query", "schema": { "type": "integer", "minimum": 64, "maximum": 2048, "default": 512 } },
          { "name": "dot_style", "in": "query", "schema": { "type": "string", "enum": ["square", "dots", "rounded"] } },
          { "name": "marker_shape", "in": "query", "schema": { "type": "string", "enum": ["square", "rounded", "circle"] } },
          { "name": "marker_center", "in": "query", "schema": { "type": "string", "enum": ["square", "dot"] } },
          { "name": "foreground", "in": "query", "schema": { "type": "string", "pattern": "^#?[0-9a-fA-F]{6}$" } },
          { "name": "marker_color", "in": "query", "schema": { "type": "string", "pattern": "^#?[0-9a-fA-F]{6}$" } },
          { "name": "transparent", "in": "query", "schema": { "type": "boolean" } },
          { "name": "download", "in": "query", "schema": { "type": "boolean" } }
        ],
        "responses": {
          "200": { "description": "QR image", "content": { "image/png": { "schema": { "type": "string", "format": "binary" } }, "image/svg+xml": { "schema": { "type": "string" } } } },
          "422": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidationError" } } } }
        }
      }
    },
    "/api/v1/links": {
      "get": {
        "operationId": "listLinks",
        "summary": "List links",
        "description": "Scope: links:read.",
        "parameters": [
          { "name": "search", "in": "query", "schema": { "type": "string" } },
          { "name": "folder_id", "in": "query", "schema": { "type": "integer" } },
          { "name": "tag", "in": "query", "schema": { "type": "string" } },
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["active", "disabled", "archived", "expired", "exhausted", "blocked", "pending_review"] } },
          { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1 } },
          { "name": "per_page", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 } }
        ],
        "responses": { "200": { "description": "Paginated links", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Link" } }, "links": { "type": "object" }, "meta": { "type": "object" } } } } } } }
      },
      "post": {
        "operationId": "createLink",
        "summary": "Create a link",
        "description": "Scope: links:write. Only destination_url is required.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateLink" } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Link" } } } } } },
          "422": { "description": "Validation or plan-limit error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidationError" } } } }
        }
      }
    },
    "/api/v1/links/bulk": {
      "post": {
        "operationId": "bulkCreateLinks",
        "summary": "Bulk-create up to 100 links (Business plan)",
        "description": "Scope: links:write. Rows fail independently.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["links"], "properties": { "links": { "type": "array", "maxItems": 100, "items": { "$ref": "#/components/schemas/CreateLink" } } } } } } },
        "responses": { "200": { "description": "Created and failed rows", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "created": { "type": "array", "items": { "$ref": "#/components/schemas/Link" } }, "failed": { "type": "array", "items": { "type": "object", "properties": { "index": { "type": "integer" }, "error": { "type": "string" } } } } } } } } } } } }
      }
    },
    "/api/v1/links/{id}": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
      "get": { "operationId": "getLink", "summary": "Get a link", "description": "Scope: links:read.", "responses": { "200": { "description": "The link", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Link" } } } } } }, "404": { "description": "Not found or not yours" } } },
      "patch": { "operationId": "updateLink", "summary": "Update a link", "description": "Scope: links:write. Any subset of create fields plus slug. Editing destination_url requires Pro+.", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateLink" } } } }, "responses": { "200": { "description": "Updated" } } },
      "delete": { "operationId": "deleteLink", "summary": "Delete a link", "description": "Scope: links:write. Soft-deleted; stops redirecting.", "responses": { "204": { "description": "Deleted" } } }
    },
    "/api/v1/links/{id}/enable": {
      "post": { "operationId": "enableLink", "summary": "Enable a disabled link", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }], "responses": { "200": { "description": "Enabled" }, "409": { "description": "Invalid state transition" } } }
    },
    "/api/v1/links/{id}/disable": {
      "post": { "operationId": "disableLink", "summary": "Disable an active link", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }], "responses": { "200": { "description": "Disabled" }, "409": { "description": "Invalid state transition" } } }
    },
    "/api/v1/links/{id}/qr": {
      "get": {
        "operationId": "getLinkQr",
        "summary": "QR code image for a link",
        "description": "Scope: links:read. Encodes the short URL, so destination edits never invalidate printed codes.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } },
          { "name": "format", "in": "query", "schema": { "type": "string", "enum": ["png", "svg"], "default": "png" } },
          { "name": "size", "in": "query", "schema": { "type": "integer", "minimum": 64, "maximum": 2048 } }
        ],
        "responses": { "200": { "description": "QR image", "content": { "image/png": {}, "image/svg+xml": {} } } }
      }
    },
    "/api/v1/links/{id}/analytics": {
      "get": {
        "operationId": "getLinkAnalytics",
        "summary": "Click analytics for a link",
        "description": "Scope: analytics:read. Bot traffic excluded except totals.botClicks; uniques are a privacy-conscious 24h estimate.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } },
          { "name": "from", "in": "query", "schema": { "type": "string", "format": "date" } },
          { "name": "to", "in": "query", "schema": { "type": "string", "format": "date" } }
        ],
        "responses": { "200": { "description": "Analytics", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Analytics" } } } } } } }
      }
    },
    "/api/v1/folders": { "get": { "operationId": "listFolders", "summary": "List folders", "description": "Scope: links:read.", "responses": { "200": { "description": "Folders" } } } },
    "/api/v1/tags": { "get": { "operationId": "listTags", "summary": "List tags", "description": "Scope: links:read.", "responses": { "200": { "description": "Tags" } } } },
    "/api/v1/account/usage": { "get": { "operationId": "getAccountUsage", "summary": "Plan, usage, and limits", "responses": { "200": { "description": "Usage", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Usage" } } } } } } } } }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Sanctum bearer token created at https://in.bio/settings (API access is a Pro/Business feature). Token abilities act as scopes: links:read, links:write, analytics:read. See https://in.bio/auth.md"
      }
    },
    "schemas": {
      "ShortenResult": {
        "type": "object",
        "properties": {
          "short_url": { "type": "string", "format": "uri" },
          "slug": { "type": "string" },
          "qr_url": { "type": "string", "format": "uri" },
          "preview_url": { "type": "string", "format": "uri" },
          "claim_url": { "type": "string", "format": "uri", "description": "Sign-up URL that attaches this anonymous link to the new account" },
          "expires": { "type": "string" },
          "docs": { "type": "string", "format": "uri" }
        }
      },
      "CreateLink": {
        "type": "object",
        "required": ["destination_url"],
        "properties": {
          "destination_url": { "type": "string", "format": "uri", "maxLength": 2048 },
          "slug": { "type": "string", "minLength": 4, "maxLength": 64, "pattern": "^[a-zA-Z0-9-_]+$" },
          "title": { "type": "string" },
          "description": { "type": "string" },
          "notes": { "type": "string" },
          "redirect_type": { "type": "integer", "enum": [301, 302, 307], "default": 302 },
          "folder_id": { "type": "integer" },
          "tags": { "type": "array", "items": { "type": "string" } },
          "expires_at": { "type": "string", "format": "date-time", "description": "Pro+" },
          "fallback_url": { "type": "string", "format": "uri", "description": "Pro+" },
          "click_limit": { "type": "integer", "description": "Pro+" },
          "password": { "type": "string", "description": "Pro+" },
          "utm": { "type": "object", "properties": { "source": { "type": "string" }, "medium": { "type": "string" }, "campaign": { "type": "string" }, "term": { "type": "string" }, "content": { "type": "string" } } }
        }
      },
      "Link": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "slug": { "type": "string" },
          "short_url": { "type": "string", "format": "uri" },
          "destination_url": { "type": "string", "format": "uri" },
          "title": { "type": ["string", "null"] },
          "description": { "type": ["string", "null"] },
          "status": { "type": "string" },
          "redirect_type": { "type": "integer" },
          "folder_id": { "type": ["integer", "null"] },
          "tags": { "type": "array", "items": { "type": "string" } },
          "expires_at": { "type": ["string", "null"], "format": "date-time" },
          "click_limit": { "type": ["integer", "null"] },
          "has_password": { "type": "boolean" },
          "total_clicks": { "type": "integer" },
          "unique_clicks": { "type": "integer" },
          "last_clicked_at": { "type": ["string", "null"], "format": "date-time" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "Analytics": {
        "type": "object",
        "properties": {
          "range": { "type": "object", "properties": { "from": { "type": "string", "format": "date" }, "to": { "type": "string", "format": "date" } } },
          "totals": { "type": "object", "properties": { "clicks": { "type": "integer" }, "uniques": { "type": "integer" }, "botClicks": { "type": "integer" } } },
          "series": { "type": "array", "items": { "type": "object", "properties": { "date": { "type": "string", "format": "date" }, "clicks": { "type": "integer" }, "uniques": { "type": "integer" } } } },
          "countries": { "type": "array", "items": { "$ref": "#/components/schemas/CountRow" } },
          "devices": { "type": "array", "items": { "$ref": "#/components/schemas/CountRow" } },
          "browsers": { "type": "array", "items": { "$ref": "#/components/schemas/CountRow" } },
          "referrers": { "type": "array", "items": { "$ref": "#/components/schemas/CountRow" } }
        }
      },
      "CountRow": { "type": "object", "properties": { "value": { "type": "string" }, "clicks": { "type": "integer" } } },
      "Usage": {
        "type": "object",
        "properties": {
          "plan": { "type": "string" },
          "period_start": { "type": "string", "format": "date" },
          "usage": { "type": "object" },
          "limits": { "type": "object" }
        }
      },
      "ValidationError": {
        "type": "object",
        "properties": { "message": { "type": "string" }, "errors": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "string" } } } }
      }
    }
  }
}
