{
  "openapi": "3.1.0",
  "info": {
    "title": "Yeeet API",
    "version": "1.0.0",
    "description": "Atomic static-site deployments for humans, agents, and CI."
  },
  "servers": [{ "url": "/" }],
  "components": {
    "securitySchemes": {
      "bearer": { "type": "http", "scheme": "bearer" },
      "apiKey": { "type": "apiKey", "in": "header", "name": "X-API-Key" }
    },
    "schemas": {
      "ManifestFile": {
        "type": "object",
        "required": ["path", "size"],
        "properties": {
          "path": { "type": "string", "examples": ["index.html"] },
          "size": { "type": "integer", "minimum": 0 },
          "contentType": { "type": "string" },
          "checksum": {
            "type": "string",
            "pattern": "^[a-fA-F0-9]{64}$",
            "description": "Optional SHA-256 digest used to reuse unchanged content."
          }
        }
      },
      "ApiError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "string" },
              "message": { "type": "string" },
              "details": {}
            }
          }
        }
      }
    }
  },
  "security": [{ "bearer": [] }, { "apiKey": [] }],
  "paths": {
    "/api/v1/me": {
      "get": {
        "operationId": "getCurrentUser",
        "responses": {
          "200": { "description": "Current user" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/v1/sites": {
      "get": {
        "operationId": "listSites",
        "responses": {
          "200": { "description": "Live sites" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/v1/sites/{slug}": {
      "delete": {
        "operationId": "deleteOwnedSite",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Site, versions, domains, and objects deleted"
          },
          "404": { "description": "Site not found or not owned by the actor" }
        }
      }
    },
    "/api/v1/sites/{slug}/versions": {
      "get": {
        "operationId": "listSiteVersions",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Immutable version history and preview URLs"
          },
          "404": { "description": "Site not found" }
        }
      }
    },
    "/api/v1/sites/{slug}/analytics": {
      "get": {
        "operationId": "getSiteAnalytics",
        "description": "Return privacy-preserving aggregate page views. No IP addresses, cookies, user agents, referrers, or visitor identifiers are collected.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 90,
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Daily page-view totals, status classes, and normalized top paths"
          },
          "404": { "description": "Site not found" }
        }
      }
    },
    "/api/v1/sites/{slug}/versions/{deploymentId}/activate": {
      "post": {
        "operationId": "activateSiteVersion",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "deploymentId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "minLength": 8 }
          }
        ],
        "responses": {
          "200": { "description": "Version atomically made live" },
          "409": { "description": "Version is ambiguous or not ready" }
        }
      }
    },
    "/api/v1/sites/{slug}/versions/{deploymentId}": {
      "delete": {
        "operationId": "deleteSiteVersion",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "deploymentId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "minLength": 8 }
          }
        ],
        "responses": {
          "200": {
            "description": "Version and stored objects deleted; replacement live version returned"
          },
          "404": { "description": "Version not found" }
        }
      }
    },
    "/api/v1/sites/{slug}/versions/{deploymentId}/access": {
      "patch": {
        "operationId": "updateSiteVersionAccess",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "deploymentId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "minLength": 8 }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "password": {
                    "oneOf": [
                      { "type": "string", "minLength": 8, "maxLength": 128 },
                      { "type": "null" }
                    ],
                    "description": "Set/change a password, or null to make public."
                  },
                  "rotateShareLink": {
                    "type": "boolean",
                    "description": "Invalidate the previous link and viewer cookies."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Updated privacy state and share URL" },
          "400": { "description": "Invalid password or empty access update" }
        }
      }
    },
    "/api/v1/sites/{slug}/domains": {
      "get": {
        "operationId": "listCustomDomains",
        "responses": {
          "200": {
            "description": "Custom domains, DNS records, and TLS status"
          }
        }
      },
      "post": {
        "operationId": "createCustomDomain",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["domain"],
                "properties": {
                  "domain": {
                    "type": "string",
                    "examples": ["docs.example.com"]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Domain attached; required DNS records returned"
          },
          "409": { "description": "Domain unavailable or already attached" },
          "503": {
            "description": "Railway domain automation is not configured"
          }
        }
      }
    },
    "/api/v1/sites/{slug}/domains/{domainId}/refresh": {
      "post": {
        "operationId": "refreshCustomDomain",
        "responses": {
          "200": { "description": "Current Railway DNS and certificate status" }
        }
      }
    },
    "/api/v1/sites/{slug}/domains/{domainId}": {
      "delete": {
        "operationId": "deleteCustomDomain",
        "responses": {
          "200": { "description": "Domain detached from Railway and Yeeet" }
        }
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "operationId": "listWebhookEndpoints",
        "responses": {
          "200": {
            "description": "Webhook endpoints and supported event names"
          }
        }
      },
      "post": {
        "operationId": "createWebhookEndpoint",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["url"],
                "properties": {
                  "url": { "type": "string", "format": "uri" },
                  "label": { "type": "string", "maxLength": 100 },
                  "events": {
                    "type": "array",
                    "items": {
                      "enum": [
                        "*",
                        "deployment.ready",
                        "deployment.activated",
                        "deployment.deleted",
                        "site.deleted",
                        "channel.updated",
                        "channel.deleted"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created; signing secret shown once"
          },
          "400": { "description": "Unsafe URL or unsupported event" }
        }
      }
    },
    "/api/v1/webhooks/{webhookId}": {
      "parameters": [
        {
          "name": "webhookId",
          "in": "path",
          "required": true,
          "schema": { "type": "string", "format": "uuid" }
        }
      ],
      "patch": {
        "operationId": "updateWebhookEndpoint",
        "responses": {
          "200": {
            "description": "Webhook updated; a rotated secret is shown once"
          }
        }
      },
      "delete": {
        "operationId": "deleteWebhookEndpoint",
        "responses": {
          "200": { "description": "Webhook and delivery history deleted" }
        }
      }
    },
    "/api/v1/webhooks/deliveries": {
      "get": {
        "operationId": "listWebhookDeliveries",
        "responses": {
          "200": { "description": "Recent durable webhook delivery attempts" }
        }
      }
    },
    "/api/v1/webhooks/deliveries/{deliveryId}/retry": {
      "post": {
        "operationId": "retryWebhookDelivery",
        "description": "Queue a failed delivery belonging to the current account. Requires an active endpoint. Pending and successful deliveries cannot be replayed.",
        "parameters": [
          {
            "name": "deliveryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Delivery queued for another attempt"
          },
          "404": {
            "description": "Delivery not found"
          },
          "409": {
            "description": "Endpoint disabled or delivery not failed"
          }
        }
      }
    },
    "/api/v1/deployments": {
      "get": {
        "operationId": "listDeployments",
        "responses": { "200": { "description": "Recent deployments" } }
      },
      "post": {
        "operationId": "createDeployment",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": { "type": "string", "maxLength": 128 },
            "description": "Returns or resumes the same deployment for identical input; conflicting reuse returns 409."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["files"],
                "properties": {
                  "slug": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
                    "description": "Optional. A readable random subdomain is generated when omitted."
                  },
                  "spaFallback": {
                    "type": "boolean",
                    "default": true,
                    "description": "Serve index.html for unmatched browser navigation paths."
                  },
                  "password": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 128,
                    "description": "Optional password protection. Completion returns a signed one-click share URL."
                  },
                  "channel": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,31}$",
                    "description": "Optional mutable, no-index channel to update instead of production."
                  },
                  "dryRun": {
                    "type": "boolean",
                    "default": false,
                    "description": "Return a file and byte diff without creating or uploading."
                  },
                  "idempotencyKey": {
                    "type": "string",
                    "maxLength": 128,
                    "description": "Body alternative to the Idempotency-Key header."
                  },
                  "source": { "enum": ["web", "cli", "api"] },
                  "files": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 5000,
                    "items": { "$ref": "#/components/schemas/ManifestFile" }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Dry-run deployment diff" },
          "201": {
            "description": "Deployment plan created. uploadUrls contains only content that was not reused inside storage."
          },
          "400": { "description": "Invalid manifest" },
          "409": {
            "description": "Site name conflict or idempotency key reused with different input"
          }
        }
      }
    },
    "/api/v1/sites/{slug}/channels": {
      "get": {
        "operationId": "listSiteChannels",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": { "description": "Mutable no-index deployment channels" }
        }
      }
    },
    "/api/v1/sites/{slug}/channels/{channel}": {
      "put": {
        "operationId": "setSiteChannel",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "channel",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["version"],
                "properties": { "version": { "type": "string" } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Channel assigned to a ready version" }
        }
      },
      "delete": {
        "operationId": "deleteSiteChannel",
        "responses": {
          "200": { "description": "Channel alias removed; version retained" }
        }
      }
    },
    "/api/v1/deployments/{deploymentId}/complete": {
      "post": {
        "operationId": "completeDeployment",
        "parameters": [
          {
            "name": "deploymentId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "Deployment verified and atomically published"
          },
          "422": {
            "description": "One or more objects are missing or incomplete"
          }
        }
      }
    },
    "/api/v1/admin": {
      "get": {
        "operationId": "getAdminOverview",
        "responses": {
          "200": {
            "description": "Users, sites, invitations, counts, and audit events"
          },
          "403": { "description": "Administrator access required" }
        }
      }
    },
    "/api/v1/admin/invitations": {
      "post": {
        "operationId": "createInvitation",
        "responses": {
          "201": {
            "description": "Invitation created; plaintext code is returned once"
          }
        }
      }
    },
    "/api/v1/admin/invitations/{invitationId}/revoke": {
      "post": {
        "operationId": "revokeInvitation",
        "responses": { "200": { "description": "Invitation revoked" } }
      }
    },
    "/api/v1/admin/users/{userId}/ban": {
      "post": {
        "operationId": "suspendUser",
        "responses": {
          "200": { "description": "User suspended and active sessions revoked" }
        }
      }
    },
    "/api/v1/admin/users/{userId}/unban": {
      "post": {
        "operationId": "reinstateUser",
        "responses": { "200": { "description": "User reinstated" } }
      }
    },
    "/api/v1/admin/users/{userId}/role": {
      "post": {
        "operationId": "setUserRole",
        "responses": { "200": { "description": "User role changed" } }
      }
    },
    "/api/v1/admin/sites/{siteId}": {
      "delete": {
        "operationId": "deleteSite",
        "responses": {
          "200": { "description": "Site, versions, and stored objects deleted" }
        }
      }
    }
  }
}
