{
  "openapi": "3.0.3",
  "info": {
    "title": "Dropley API",
    "version": "1.0.0",
    "description": "API for publishing static artifacts via Dropley.\n\nDropley turns an `index.html`-based static bundle into a shareable URL that expires automatically. Uploads are multipart form posts; everything else is plain JSON over HTTPS.\n\n## Versioning & deprecation policy\n\n- The current major version is **v1**. The canonical OpenAPI document lives at a URL-versioned path (`/api/v1/openapi.json`); future major versions will be published in parallel under their own path (e.g. `/api/v2/`) — v1 keeps working unchanged.\n- Within v1 the REST surface is stable: no breaking changes to existing endpoints, request/response shapes, or status codes. Additions (new optional fields, new endpoints) may ship at any time.\n- Deprecations are announced **at least 90 days before removal** via three channels: this description, the machine-readable changelog at `/CHANGELOG.json`, and runtime response headers on affected operations — `Deprecation` (RFC 9745, structured date when the operation became deprecated) and `Sunset` (RFC 8594, HTTP-date after which it stops working).\n- Clients SHOULD treat the presence of `Deprecation`/`Sunset` as a migration signal and follow the linked guidance; absence of these headers means the operation is not deprecated.\n\n## Error handling\n\nEvery JSON 4xx/5xx response uses one of two documented shapes:\n\n- **Flat envelope** (`ErrorResponse` / `InternalErrorResponse`): `error` (human-readable message, legacy alias), `code` (machine-readable SCREAMING_SNAKE identifier), `message` (human-readable), plus optional `hint` / `requestId`. Known codes: VALIDATION_ERROR, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, METHOD_NOT_ALLOWED, PAYLOAD_TOO_LARGE, PROCESSING_ERROR, INTERNAL_ERROR.\n- **Rate limit envelope** (`RateLimitedErrorResponse`): `{ \"error\": { \"code\": \"RATE_LIMITED\", \"message\": \"...\" } }` with a `Retry-After` header.\n\nMatch on `code`, never on message text.\n\n## Rate limiting\n\nAll endpoints are rate limited per client. Every throttled endpoint returns standard quota headers on success and failure: `RateLimit-Policy` (`\"default\";q=<quota>;w=<window-seconds>`), `RateLimit` (`\"default\";r=<remaining>;t=<reset-seconds>`) per draft-ietf-httpapi-ratelimit-headers, plus the widely deployed legacy fields `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. When a request is rejected with 429, `Retry-After` carries the exact wait in seconds. Default quotas: 5 uploads/hour (POST /api/artifacts), 20/min for PATCH/DELETE mutations, 300/min for artifact reads."
  },
  "servers": [
    {
      "url": "https://dropley.app",
      "description": "Production"
    },
    {
      "url": "http://localhost:8788",
      "description": "Local preview"
    }
  ],
  "components": {
    "securitySchemes": {
      "cookieAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "session"
      },
      "artifactToken": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Artifact-Token",
        "description": "Artifact owner token from artifactToken in upload response — used for PATCH/DELETE mutation auth on artifact endpoints"
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message (legacy alias of message)",
            "example": "Not found"
          },
          "code": {
            "type": "string",
            "enum": [
              "VALIDATION_ERROR",
              "UNAUTHORIZED",
              "FORBIDDEN",
              "NOT_FOUND",
              "METHOD_NOT_ALLOWED",
              "PAYLOAD_TOO_LARGE",
              "PROCESSING_ERROR",
              "INTERNAL_ERROR"
            ],
            "description": "Machine-readable error identifier — match on this, never on message text",
            "example": "NOT_FOUND"
          },
          "message": {
            "type": "string",
            "description": "Human-readable error message",
            "example": "Not found"
          },
          "hint": {
            "type": "string",
            "description": "Optional discovery hint pointing at the API index or OpenAPI spec"
          },
          "requestId": {
            "type": "string",
            "description": "Request correlation ID (x-request-id) when available",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          }
        },
        "required": [
          "error",
          "code",
          "message"
        ],
        "description": "Flat error envelope returned by all JSON error responses except rate limiting. Known codes: VALIDATION_ERROR (400/422), UNAUTHORIZED (401), FORBIDDEN (403), NOT_FOUND (404), METHOD_NOT_ALLOWED (405), PAYLOAD_TOO_LARGE (413), PROCESSING_ERROR (500), INTERNAL_ERROR (500)."
      },
      "InternalErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message (legacy alias of message)",
            "example": "Internal server error"
          },
          "code": {
            "type": "string",
            "enum": [
              "INTERNAL_ERROR"
            ],
            "description": "Machine-readable error identifier",
            "example": "INTERNAL_ERROR"
          },
          "message": {
            "type": "string",
            "description": "Human-readable error message",
            "example": "Internal server error"
          },
          "requestId": {
            "type": "string",
            "format": "uuid",
            "description": "Request correlation ID for log/Sentry lookup",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          }
        },
        "required": [
          "error",
          "code",
          "message",
          "requestId"
        ],
        "description": "Unexpected server error with a correlation ID usable in support requests."
      },
      "RateLimitedErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "RATE_LIMITED"
                ],
                "example": "RATE_LIMITED"
              },
              "message": {
                "type": "string",
                "example": "Too many requests. Please wait 30 seconds before retrying."
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ],
        "description": "Rate limit exceeded. Wait for the seconds given in Retry-After (also mirrored in RateLimit-Reset) before retrying."
      }
    },
    "parameters": {}
  },
  "paths": {
    "/api/artifacts/{shortId}": {
      "get": {
        "operationId": "getArtifact",
        "summary": "Get artifact data",
        "description": "Returns artifact data (public fields without auth; private fields with ?token=).",
        "tags": [
          "Artifacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "The 8-character short ID of the artifact",
              "example": "aB3cD5eF"
            },
            "required": true,
            "name": "shortId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "description": "Artifact token for authorized access to private fields",
              "example": "tok_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p"
            },
            "required": false,
            "name": "token",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Artifact data",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "shortId": {
                      "type": "string",
                      "example": "aB3cD5eF"
                    },
                    "status": {
                      "type": "string",
                      "example": "ready"
                    },
                    "url": {
                      "type": "string",
                      "example": "https://dropley.app/p/aB3cD5eF"
                    },
                    "error": {
                      "type": "string",
                      "example": "Processing failed: entry file index.html not found"
                    },
                    "expiresAt": {
                      "type": "string",
                      "nullable": true,
                      "example": "2026-07-22T12:00:00.000Z"
                    },
                    "source": {
                      "type": "string",
                      "example": "other"
                    },
                    "tags": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "example": [
                        "production",
                        "v1.0.0"
                      ]
                    },
                    "createdAt": {
                      "type": "string",
                      "example": "2026-07-12T12:00:00.000Z"
                    }
                  },
                  "required": [
                    "shortId",
                    "status"
                  ],
                  "description": "Artifact data. Public fields returned without auth; pass ?token= for private fields."
                }
              }
            }
          },
          "404": {
            "description": "Short ID not found",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponse"
                    },
                    {
                      "description": "Not found error"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests). Present on window-exhaustion rejections; omitted on temporary abuse blocks.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window; 0 on every throttled response. Omitted on temporary abuse blocks.",
                "schema": {
                  "type": "integer"
                },
                "example": 0
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>. Present on window-exhaustion rejections; omitted on temporary abuse blocks.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying a throttled request (RFC 7231 delay-seconds).",
                "schema": {
                  "type": "integer"
                },
                "example": 30
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/RateLimitedErrorResponse"
                    },
                    {
                      "description": "Rate limit exceeded"
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/InternalErrorResponse"
                    },
                    {
                      "description": "Unexpected server error"
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateArtifact",
        "summary": "Update artifact (source, expiry, tags)",
        "description": "Updates one or more fields of a published artifact. At least one field is required.",
        "tags": [
          "Artifacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "The 8-character short ID of the artifact",
              "example": "aB3cD5eF"
            },
            "required": true,
            "name": "shortId",
            "in": "path"
          }
        ],
        "requestBody": {
          "description": "Fields to update on the artifact",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "source": {
                    "type": "string",
                    "enum": [
                      "claude-code",
                      "chatgpt",
                      "cursor",
                      "lovable",
                      "bolt",
                      "storybook",
                      "figma",
                      "other"
                    ],
                    "example": "other"
                  },
                  "expiry": {
                    "type": "string",
                    "enum": [
                      "1d",
                      "3d",
                      "7d"
                    ],
                    "example": "3d"
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 50
                    },
                    "maxItems": 10,
                    "example": [
                      "updated",
                      "v1.1.0"
                    ]
                  }
                },
                "description": "Patch artifact payload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Artifact updated successfully",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "source": {
                      "type": "string",
                      "example": "other"
                    },
                    "expiresAt": {
                      "type": "string",
                      "nullable": true,
                      "example": "2026-07-22T12:00:00.000Z"
                    },
                    "tags": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "example": [
                        "updated",
                        "v1.1.0"
                      ]
                    }
                  },
                  "required": [
                    "success"
                  ],
                  "description": "Success response with updated fields"
                }
              }
            }
          },
          "400": {
            "description": "Invalid fields or malformed request",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponse"
                    },
                    {
                      "description": "Error response body (code VALIDATION_ERROR)"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing artifact token",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponse"
                    },
                    {
                      "description": "Authentication required (code UNAUTHORIZED)"
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Invalid artifact token",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponse"
                    },
                    {
                      "description": "Forbidden (code FORBIDDEN)"
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Artifact not found",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponse"
                    },
                    {
                      "description": "Not found error (code NOT_FOUND)"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests). Present on window-exhaustion rejections; omitted on temporary abuse blocks.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window; 0 on every throttled response. Omitted on temporary abuse blocks.",
                "schema": {
                  "type": "integer"
                },
                "example": 0
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>. Present on window-exhaustion rejections; omitted on temporary abuse blocks.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying a throttled request (RFC 7231 delay-seconds).",
                "schema": {
                  "type": "integer"
                },
                "example": 30
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/RateLimitedErrorResponse"
                    },
                    {
                      "description": "Rate limit exceeded"
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/InternalErrorResponse"
                    },
                    {
                      "description": "Unexpected server error"
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteArtifact",
        "summary": "Delete an artifact",
        "description": "Permanently deletes an artifact and its files from storage. Requires artifact token or admin auth.",
        "tags": [
          "Artifacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "The 8-character short ID of the artifact",
              "example": "aB3cD5eF"
            },
            "required": true,
            "name": "shortId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "description": "Artifact owner token",
              "example": "tok_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p"
            },
            "required": false,
            "name": "X-Artifact-Token",
            "in": "header"
          },
          {
            "schema": {
              "type": "string",
              "description": "Bearer <admin-key>",
              "example": "Bearer dropley_publish_abc123def456"
            },
            "required": false,
            "name": "Authorization",
            "in": "header"
          }
        ],
        "responses": {
          "200": {
            "description": "Artifact deleted successfully",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    }
                  },
                  "required": [
                    "success"
                  ],
                  "description": "Deletion success response"
                }
              }
            }
          },
          "401": {
            "description": "Missing artifact token",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponse"
                    },
                    {
                      "description": "Authentication required (code UNAUTHORIZED)"
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Invalid artifact token",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponse"
                    },
                    {
                      "description": "Forbidden (code FORBIDDEN)"
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Artifact not found",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponse"
                    },
                    {
                      "description": "Not found error (code NOT_FOUND)"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests). Present on window-exhaustion rejections; omitted on temporary abuse blocks.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window; 0 on every throttled response. Omitted on temporary abuse blocks.",
                "schema": {
                  "type": "integer"
                },
                "example": 0
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>. Present on window-exhaustion rejections; omitted on temporary abuse blocks.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying a throttled request (RFC 7231 delay-seconds).",
                "schema": {
                  "type": "integer"
                },
                "example": 30
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/RateLimitedErrorResponse"
                    },
                    {
                      "description": "Rate limit exceeded"
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/InternalErrorResponse"
                    },
                    {
                      "description": "Unexpected server error"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/artifacts": {
      "post": {
        "operationId": "createArtifact",
        "summary": "Upload files as an artifact",
        "description": "Accepts a manifest JSON string and positional file parts to create a published artifact. Returns 201 on success with the public URL.",
        "tags": [
          "Artifacts"
        ],
        "requestBody": {
          "description": "Manifest JSON string and positional file parts",
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "manifest": {
                    "type": "string",
                    "description": "JSON string containing artifact manifest with version, entry point, and file list",
                    "example": "{\"manifestVersion\":1,\"entry\":\"index.html\",\"files\":[{\"path\":\"index.html\",\"size\":2048,\"contentType\":\"text/html\"},{\"path\":\"style.css\",\"size\":512,\"contentType\":\"text/css\"},{\"path\":\"app.js\",\"size\":1024,\"contentType\":\"application/javascript\"}]}"
                  },
                  "expiry": {
                    "type": "string",
                    "enum": [
                      "1d",
                      "3d",
                      "7d"
                    ],
                    "default": "3d",
                    "example": "3d"
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "claude-code",
                      "chatgpt",
                      "cursor",
                      "lovable",
                      "bolt",
                      "storybook",
                      "figma",
                      "other"
                    ],
                    "example": "other"
                  },
                  "tags": {
                    "type": "array",
                    "nullable": true,
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 50
                    },
                    "maxItems": 10,
                    "example": [
                      "production",
                      "v1.0.0"
                    ]
                  },
                  "file": {
                    "type": "array",
                    "items": {}
                  }
                },
                "required": [
                  "manifest"
                ],
                "description": "Artifact upload request payload"
              },
              "examples": {
                "minimal": {
                  "summary": "Single index.html upload",
                  "description": "Minimal upload with one HTML file and 7-day expiry",
                  "value": {
                    "manifest": "{\"manifestVersion\":1,\"entry\":\"index.html\",\"files\":[{\"path\":\"index.html\",\"size\":13,\"contentType\":\"text/html\"}]}",
                    "file": "(binary)",
                    "expiry": "7d"
                  }
                },
                "with_tags_and_source": {
                  "summary": "Upload with tags and source",
                  "description": "Upload with metadata identifying the source AI agent and version tags",
                  "value": {
                    "manifest": "{\"manifestVersion\":1,\"entry\":\"index.html\",\"files\":[{\"path\":\"index.html\",\"size\":2048,\"contentType\":\"text/html\"},{\"path\":\"style.css\",\"size\":512,\"contentType\":\"text/css\"},{\"path\":\"app.js\",\"size\":1024,\"contentType\":\"application/javascript\"}]}",
                    "file": "(binary)",
                    "expiry": "7d",
                    "source": "claude-code",
                    "tags": "production,v1.0.0"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Artifact created and published successfully",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "shortId": {
                      "type": "string",
                      "description": "The 8-character short ID for the artifact",
                      "example": "aB3cD5eF"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri",
                      "description": "Public URL to access the artifact",
                      "example": "https://dropley.app/p/aB3cD5eF"
                    },
                    "expiresAt": {
                      "type": "string",
                      "nullable": true,
                      "description": "ISO 8601 timestamp when the artifact expires (null = never)",
                      "example": "2026-07-22T12:00:00.000Z"
                    },
                    "artifactToken": {
                      "type": "string",
                      "description": "Token for mutating the artifact (PATCH/DELETE)",
                      "example": "tok_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p"
                    }
                  },
                  "required": [
                    "shortId",
                    "url",
                    "expiresAt",
                    "artifactToken"
                  ],
                  "description": "Published artifact with URL and token"
                }
              }
            }
          },
          "413": {
            "description": "Total file size exceeds maximum allowed (50MB)",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponse"
                    },
                    {
                      "description": "Error response body (code PAYLOAD_TOO_LARGE)"
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Validation error (missing manifest field, non-string or malformed manifest, invalid manifest version, missing file contentType, entry not named index.html, file count mismatch, disallowed file type, path violations, duplicate paths)",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponse"
                    },
                    {
                      "description": "Validation error (code VALIDATION_ERROR)"
                    }
                  ]
                },
                "examples": {
                  "missing_index_html": {
                    "summary": "Entry document not named index.html",
                    "value": {
                      "error": "Entry document must be named index.html",
                      "code": "VALIDATION_ERROR",
                      "message": "Entry document must be named index.html"
                    }
                  },
                  "missing_content_type": {
                    "summary": "Missing contentType on file entry",
                    "value": {
                      "error": "Required",
                      "code": "VALIDATION_ERROR",
                      "message": "Required"
                    }
                  },
                  "invalid_file_type": {
                    "summary": "Disallowed file extension",
                    "value": {
                      "error": "File type not allowed: foo.exe",
                      "code": "VALIDATION_ERROR",
                      "message": "File type not allowed: foo.exe"
                    }
                  },
                  "file_count_mismatch": {
                    "summary": "File count mismatch between manifest and form data",
                    "value": {
                      "error": "Manifest lists 2 files but FormData has 1 file parts",
                      "code": "VALIDATION_ERROR",
                      "message": "Manifest lists 2 files but FormData has 1 file parts"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests). Present on window-exhaustion rejections; omitted on temporary abuse blocks.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window; 0 on every throttled response. Omitted on temporary abuse blocks.",
                "schema": {
                  "type": "integer"
                },
                "example": 0
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>. Present on window-exhaustion rejections; omitted on temporary abuse blocks.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying a throttled request (RFC 7231 delay-seconds).",
                "schema": {
                  "type": "integer"
                },
                "example": 30
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/RateLimitedErrorResponse"
                    },
                    {
                      "description": "Rate limit error with retry info"
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/InternalErrorResponse"
                    },
                    {
                      "description": "Unexpected server error"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/report": {
      "post": {
        "operationId": "createReport",
        "summary": "Submit an abuse report",
        "description": "Accepts application/x-www-form-urlencoded form data to report an artifact for abuse. Returns HTML (form with errors or confirmation page), not JSON.",
        "tags": [
          "Reports"
        ],
        "requestBody": {
          "description": "Report form fields",
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "artifact": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Artifact short ID",
                    "example": "aB3cD5eF"
                  },
                  "reason": {
                    "type": "string",
                    "enum": [
                      "copyright",
                      "inappropriate",
                      "spam",
                      "other"
                    ],
                    "description": "Report reason category",
                    "example": "spam"
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 1000,
                    "description": "Optional description (max 1000 chars)",
                    "example": "This artifact contains spam links."
                  },
                  "contact": {
                    "type": "string",
                    "format": "email",
                    "description": "Optional contact email",
                    "example": "reporter@example.com"
                  }
                },
                "required": [
                  "artifact",
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Report submitted successfully (HTML confirmation page)",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "text/html": {
                "schema": {
                  "type": "string",
                  "description": "HTML confirmation page"
                }
              }
            }
          },
          "404": {
            "description": "Artifact not found or not published (HTML 404 page)",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests).",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer"
                },
                "example": 4
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "text/html": {
                "schema": {
                  "type": "string",
                  "description": "HTML 404 page"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "headers": {
              "RateLimit-Limit": {
                "description": "Quota allocated for the current window (requests). Present on window-exhaustion rejections; omitted on temporary abuse blocks.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window; 0 on every throttled response. Omitted on temporary abuse blocks.",
                "schema": {
                  "type": "integer"
                },
                "example": 0
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current window resets.",
                "schema": {
                  "type": "integer"
                },
                "example": 42
              },
              "RateLimit": {
                "description": "Current service limit per draft-ietf-httpapi-ratelimit-headers: \"default\";r=<remaining>;t=<reset-seconds>.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";r=4;t=42"
              },
              "RateLimit-Policy": {
                "description": "Quota policy per draft-ietf-httpapi-ratelimit-headers: \"default\";q=<quota>;w=<window-seconds>. Present on window-exhaustion rejections; omitted on temporary abuse blocks.",
                "schema": {
                  "type": "string"
                },
                "example": "\"default\";q=5;w=3600"
              },
              "Deprecation": {
                "description": "RFC 9745 structured date (@<unix-timestamp>) marking when this operation was or will be deprecated. Absent while the operation is stable.",
                "schema": {
                  "type": "string"
                }
              },
              "Sunset": {
                "description": "RFC 8594 HTTP-date after which this operation is expected to become unresponsive. Never earlier than Deprecation.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying a throttled request (RFC 7231 delay-seconds).",
                "schema": {
                  "type": "integer"
                },
                "example": 30
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/RateLimitedErrorResponse"
                    },
                    {
                      "description": "Rate limit exceeded"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  }
}