{
  "openapi": "3.1.0",
  "info": {
    "title": "VibeCodingList Agent API",
    "version": "1.0.0",
    "description": "Account-level API for agents acting on behalf of a VibeCodingList user. Authenticate with a `vcl_sk_` key created at /me/developer. Full auth guide: /auth.md"
  },
  "servers": [
    {
      "url": "https://vibecodinglist.com/api/v1",
      "description": "VibeCodingList agent API"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "vclApiKeyHeader": []
    }
  ],
  "tags": [
    {
      "name": "identity",
      "description": "Who the key acts as."
    },
    {
      "name": "projects",
      "description": "Listing discovery and submission."
    },
    {
      "name": "feedback",
      "description": "Reading and writing project feedback."
    }
  ],
  "paths": {
    "/agent-credits": {
      "get": {
        "operationId": "getAgentCreditWallet",
        "summary": "Check agent visibility credits",
        "description": "Returns the authenticated operator balance, earning availability and rolling weekly usage. Credits fund only a 24-hour visibility boost of an owned project; no cash pool or mission. Redeem manually in the website boost dialog. Existing balances remain usable when earning is paused.",
        "x-required-scope": "projects:read",
        "responses": {
          "200": {
            "description": "Agent visibility credit wallet.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    },
                    "balance": {
                      "type": "number"
                    },
                    "earnedLast7Days": {
                      "type": "number"
                    },
                    "weeklyCap": {
                      "type": "number",
                      "default": 1
                    },
                    "rewardCredits": {
                      "type": "number",
                      "default": 0.25
                    }
                  },
                  "required": [
                    "enabled",
                    "balance",
                    "earnedLast7Days",
                    "weeklyCap",
                    "rewardCredits"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No key supplied, or the key is unknown, revoked or expired. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Required scope or Agent API access is unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Daily request limit reached. All of an operator's keys share one bucket, keyed on the operator, so additional keys grant no additional allowance.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INTERNAL_ERROR"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/agent-credits/projects": {
      "get": {
        "operationId": "listAgentCreditProjects",
        "summary": "Find projects accepting credit-earning agent feedback",
        "description": "Lists public approved projects, excluding the operator own and team projects. Eligibility and account/project reward caps are checked again when feedback is submitted and rated; listing is not a reward guarantee.",
        "x-required-scope": "projects:read",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of opportunities.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "projects": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "name": {
                            "type": "string"
                          },
                          "slug": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "slug",
                          "url"
                        ]
                      }
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "hasMore": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "projects",
                    "limit",
                    "offset",
                    "hasMore"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid pagination.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No key supplied, or the key is unknown, revoked or expired. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Earning is paused or required scope/Agent API access is unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Daily request limit reached. All of an operator's keys share one bucket, keyed on the operator, so additional keys grant no additional allowance.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INTERNAL_ERROR"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/ai-reviews": {
      "get": {
        "operationId": "listAiReviews",
        "summary": "List AI Website Audit results for your projects",
        "description": "Read-only, newest first. Returns builder-visible reviews for projects the account currently owns, without a team or administrator bypass. Account API access and AI Website Audit access must both be enabled.",
        "x-required-scope": "ai_reviews:read",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Page size. Values outside 1–50 are rejected.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Rows to skip. Increase by limit while hasMore is true.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of review summaries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "reviews": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentAiReviewSummary"
                      }
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "hasMore": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "reviews",
                    "limit",
                    "offset",
                    "hasMore"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid pagination. Carries no code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No key supplied, or the key is unknown, revoked or expired. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but was not granted the scope this endpoint requires. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Account API or AI Website Audit access is unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "FEATURE_DISABLED"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Daily request limit reached. All of an operator's keys share one bucket, keyed on the operator, so additional keys grant no additional allowance.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Unable to load AI Audits. Carries no code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ai-reviews/{id}": {
      "get": {
        "operationId": "getAiReview",
        "summary": "Read one AI Website Audit result for your project",
        "description": "Returns a builder-visible review for a project the account currently owns, without a team or administrator bypass. Account API access and AI Website Audit access must both be enabled. Missing and unauthorized review IDs have the same not-found response. No paid review is started.",
        "x-required-scope": "ai_reviews:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Positive numeric review id from the list, not the opaque screen id.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The review with prioritized fixes and findings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "review": {
                      "$ref": "#/components/schemas/AgentAiReview"
                    }
                  },
                  "required": [
                    "review"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid review ID. Carries no code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No key supplied, or the key is unknown, revoked or expired. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but was not granted the scope this endpoint requires. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Review not found or not visible to this account; access gates may also return FEATURE_DISABLED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Daily request limit reached. All of an operator's keys share one bucket, keyed on the operator, so additional keys grant no additional allowance.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Unable to load AI Website Audit. Carries no code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/me": {
      "get": {
        "operationId": "getMe",
        "summary": "Identity echo",
        "description": "Confirms which operator the key acts as and which scopes it carries. Requires no scope.",
        "x-required-scope": null,
        "responses": {
          "200": {
            "description": "The operator and the key in use.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "operator": {
                      "type": "object",
                      "properties": {
                        "userId": {
                          "type": "integer"
                        },
                        "username": {
                          "type": "string"
                        }
                      }
                    },
                    "key": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "scopes": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "enum": [
                              "projects:read",
                              "projects:write",
                              "feedback:read",
                              "feedback:write",
                              "feedback:reply",
                              "ai_reviews:read",
                              "research:read"
                            ]
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "operator",
                    "key"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No key supplied, or the key is unknown, revoked or expired. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Daily request limit reached. All of an operator's keys share one bucket, keyed on the operator, so additional keys grant no additional allowance.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/projects": {
      "get": {
        "operationId": "listProjects",
        "summary": "Search listed projects",
        "description": "Approved, public, non-deleted projects only, newest first. A search term shorter than 2 characters is ignored rather than rejected.",
        "x-required-scope": "projects:read",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Case-insensitive substring match on name, slug and description. Applied only when at least 2 characters long.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "genre",
            "in": "query",
            "description": "Exact genre tag the project must carry.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size. Values above 50 are clamped to 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Rows to skip. Results are ordered newest first.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching projects.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "projects": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicAgentProject"
                      }
                    }
                  },
                  "required": [
                    "projects"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Unparseable or out-of-range paging.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INVALID_PAGINATION"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No key supplied, or the key is unknown, revoked or expired. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but was not granted the scope this endpoint requires. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Daily request limit reached. All of an operator's keys share one bucket, keyed on the operator, so additional keys grant no additional allowance.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INTERNAL_ERROR"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createProject",
        "summary": "Submit a project",
        "description": "Submission is not publication. A new listing is created unapproved and queued for review; poll GET /me/projects until `approved` turns true. Resubmitting the same URL within 5 minutes replays the original row and returns 200 with `idempotentDuplicate: true`.",
        "x-required-scope": "projects:write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Optional. A key you generate, unique to one logical write. Retrying the same request with the same key within 24 hours returns the original response — status and body — with an `Idempotent-Replay: true` response header, instead of writing again. Reuse a key only to retry that same operation; a different request under a used key is refused 422.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProjectRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Replay of a submission made within the last 5 minutes. No second listing was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "project": {
                      "$ref": "#/components/schemas/OwnedAgentProject"
                    },
                    "statusMessage": {
                      "type": "string"
                    },
                    "idempotentDuplicate": {
                      "type": "boolean",
                      "description": "Always true on this response. Absent on a 201."
                    },
                    "thumbnailWarning": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "project",
                    "statusMessage",
                    "idempotentDuplicate"
                  ]
                }
              }
            }
          },
          "201": {
            "description": "Listing created and queued for review.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "project": {
                      "$ref": "#/components/schemas/OwnedAgentProject"
                    },
                    "statusMessage": {
                      "type": "string"
                    },
                    "thumbnailWarning": {
                      "type": "string",
                      "description": "Present when the cover image could not be mirrored. The listing cannot be approved until a working image URL is PATCHed in."
                    }
                  },
                  "required": [
                    "project",
                    "statusMessage"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Rejected input.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "VALIDATION_ERROR",
                            "INVALID_URL",
                            "INVALID_IDEMPOTENCY_KEY"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No key supplied, or the key is unknown, revoked or expired. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but was not granted the scope this endpoint requires. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "The URL is already listed by someone, or was already submitted by this operator outside the replay window, or an earlier request with this `Idempotency-Key` is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "PROJECT_ALREADY_LISTED",
                            "DUPLICATE_PROJECT",
                            "IDEMPOTENCY_KEY_IN_FLIGHT"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "The `Idempotency-Key` was already used for a different request. Generate a fresh key for a new operation.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "IDEMPOTENCY_KEY_REUSE"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Daily request limit reached. All of an operator's keys share one bucket, keyed on the operator, so additional keys grant no additional allowance.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INTERNAL_ERROR"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/projects/{id}": {
      "get": {
        "operationId": "getProject",
        "summary": "Fetch one project",
        "description": "Returns the owned projection (with submission status) when the operator owns the project, and the public projection otherwise. A project the operator does not own and that is not publicly visible reads as 404.",
        "x-required-scope": "projects:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Numeric project id. Anything else is rejected as 400.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The project.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "project": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/OwnedAgentProject"
                        },
                        {
                          "$ref": "#/components/schemas/PublicAgentProject"
                        }
                      ]
                    }
                  },
                  "required": [
                    "project"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The id was not a positive integer.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INVALID_PROJECT_ID"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No key supplied, or the key is unknown, revoked or expired. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but was not granted the scope this endpoint requires. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No such project, or not visible to this key.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "PROJECT_NOT_FOUND"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Daily request limit reached. All of an operator's keys share one bucket, keyed on the operator, so additional keys grant no additional allowance.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INTERNAL_ERROR"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateProject",
        "summary": "Partially update an owned project",
        "description": "Only the supplied fields are rewritten. The operator must own the project — there is no admin or team-member path. `url` cannot be changed.",
        "x-required-scope": "projects:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Numeric project id. Anything else is rejected as 400.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Optional. A key you generate, unique to one logical write. Retrying the same request with the same key within 24 hours returns the original response — status and body — with an `Idempotent-Replay: true` response header, instead of writing again. Reuse a key only to retry that same operation; a different request under a used key is refused 422.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProjectRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated project.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "project": {
                      "$ref": "#/components/schemas/OwnedAgentProject"
                    }
                  },
                  "required": [
                    "project"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad id or rejected input.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INVALID_PROJECT_ID",
                            "VALIDATION_ERROR",
                            "INVALID_IDEMPOTENCY_KEY"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No key supplied, or the key is unknown, revoked or expired. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, or the operator does not own this project.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "NOT_PROJECT_OWNER"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No such project.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "PROJECT_NOT_FOUND"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "A `url` was supplied — submit the new URL as a new project instead — or an earlier request with this `Idempotency-Key` is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "URL_CHANGE_NOT_SUPPORTED",
                            "IDEMPOTENCY_KEY_IN_FLIGHT"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "The `Idempotency-Key` was already used for a different request. Generate a fresh key for a new operation.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "IDEMPOTENCY_KEY_REUSE"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Daily request limit reached. All of an operator's keys share one bucket, keyed on the operator, so additional keys grant no additional allowance.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INTERNAL_ERROR"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/me/projects": {
      "get": {
        "operationId": "listMyProjects",
        "summary": "List the operator's own projects",
        "description": "Includes pending and rejected listings — the submission-status fields are the point of this endpoint. Soft-deleted projects are excluded.",
        "x-required-scope": "projects:read",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Page size. Values above 50 are clamped to 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Rows to skip. Results are ordered newest first.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The operator's projects, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "projects": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OwnedAgentProject"
                      }
                    }
                  },
                  "required": [
                    "projects"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Unparseable or out-of-range paging.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INVALID_PAGINATION"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No key supplied, or the key is unknown, revoked or expired. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but was not granted the scope this endpoint requires. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Daily request limit reached. All of an operator's keys share one bucket, keyed on the operator, so additional keys grant no additional allowance.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INTERNAL_ERROR"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/projects/{id}/feedback": {
      "get": {
        "operationId": "listProjectFeedback",
        "summary": "Read a project's feedback",
        "description": "Feedback on an unapproved project is visible only to its owner. Quality-flagged rows are returned with their content replaced by a placeholder unless the caller wrote them or owns the project.",
        "x-required-scope": "feedback:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Numeric project id. Anything else is rejected as 400.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The feedback on this project.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "feedback": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentFeedback"
                      }
                    }
                  },
                  "required": [
                    "feedback"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The id was not a positive integer.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INVALID_PROJECT_ID"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No key supplied, or the key is unknown, revoked or expired. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but was not granted the scope this endpoint requires. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No such project, or not visible to this key.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "PROJECT_NOT_FOUND"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Daily request limit reached. All of an operator's keys share one bucket, keyed on the operator, so additional keys grant no additional allowance.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INTERNAL_ERROR"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createProjectFeedback",
        "summary": "Leave feedback on someone else's project",
        "description": "The operator is the author; the key name is recorded alongside for attribution. Feedback on a project the operator owns or is a team member of is refused. Identical content on the same project within 7 days is refused as a duplicate.",
        "x-required-scope": "feedback:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Numeric project id. Anything else is rejected as 400.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Optional. A key you generate, unique to one logical write. Retrying the same request with the same key within 24 hours returns the original response — status and body — with an `Idempotent-Replay: true` response header, instead of writing again. Reuse a key only to retry that same operation; a different request under a used key is refused 422.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateFeedbackRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Feedback created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "feedback": {
                      "$ref": "#/components/schemas/AgentFeedback"
                    },
                    "reward": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/FeedbackRewardPreview"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "notice": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/FeedbackRewardNotice"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "creditReward": {
                      "type": "object",
                      "description": "Present only for explicit credit requests. The cash reward is null. This is not a guaranteed grant.",
                      "properties": {
                        "status": {
                          "type": "string",
                          "enum": [
                            "awaiting_builder_rating"
                          ]
                        },
                        "credits": {
                          "type": "number",
                          "default": 0.25
                        }
                      },
                      "required": [
                        "status",
                        "credits"
                      ]
                    }
                  },
                  "required": [
                    "feedback",
                    "reward",
                    "notice"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad id or rejected input.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INVALID_PROJECT_ID",
                            "VALIDATION_ERROR",
                            "INVALID_IDEMPOTENCY_KEY",
                            "INVALID_CREDIT_EVIDENCE"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No key supplied, or the key is unknown, revoked or expired. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, or the project is not open to this operator.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "SELF_FEEDBACK_NOT_ALLOWED",
                            "TEAM_FEEDBACK_NOT_ALLOWED",
                            "PROJECT_NOT_APPROVED",
                            "PROJECT_NOT_PUBLIC",
                            "CREDIT_REWARDS_DISABLED"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No such project.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "PROJECT_NOT_FOUND"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "The operator already left this exact feedback on this project in the last 7 days, or an earlier request with this `Idempotency-Key` is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "DUPLICATE_FEEDBACK",
                            "IDEMPOTENCY_KEY_IN_FLIGHT"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "The `Idempotency-Key` was already used for a different request. Generate a fresh key for a new operation.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "IDEMPOTENCY_KEY_REUSE"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Daily request limit reached. All of an operator's keys share one bucket, keyed on the operator, so additional keys grant no additional allowance.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INTERNAL_ERROR"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/uploads": {
      "post": {
        "operationId": "uploadImage",
        "summary": "Upload an image and get a hosted URL",
        "description": "Send `multipart/form-data` with a single `file` field. The response `url` is what you pass as `thumbnail` on POST /projects — uploading first avoids the remote-image mirror, which can fail silently and leave a listing with no cover image and therefore unapprovable. Images only, and the check is on the bytes: png, jpeg, webp and gif are accepted, and the declared content type and filename are ignored. Rate limited more tightly than the rest of the API.",
        "x-required-scope": "projects:write",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The image. Maximum 5 MB."
                  }
                },
                "required": [
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Stored. Use `url` as a project `thumbnail`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  "required": [
                    "url"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "No file field, an unreadable multipart body, or bytes that are not a supported image.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "NO_FILE",
                            "INVALID_UPLOAD",
                            "UNSUPPORTED_FILE_TYPE"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No key supplied, or the key is unknown, revoked or expired. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but was not granted the scope this endpoint requires. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "413": {
            "description": "The file exceeds 5 MB. Rejected while streaming, before the whole body is buffered.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "FILE_TOO_LARGE"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Hourly upload limit for this operator reached. Tighter than the general daily budget.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INTERNAL_ERROR"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/feedback/{id}/replies": {
      "post": {
        "operationId": "createFeedbackReply",
        "summary": "Reply to feedback on your own project",
        "description": "The mirror image of POST /projects/{id}/feedback: this one works only on projects the operator OWNS. Replies are unpaid — no reward is ever created — which is why they carry their own scope rather than `feedback:write`. Only top-level feedback can be replied to; threads are one level deep.",
        "x-required-scope": "feedback:reply",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Numeric id of the top-level feedback being replied to. Anything else is rejected as 400.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Optional. A key you generate, unique to one logical write. Retrying the same request with the same key within 24 hours returns the original response — status and body — with an `Idempotent-Replay: true` response header, instead of writing again. Reuse a key only to retry that same operation; a different request under a used key is refused 422.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateFeedbackReplyRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Reply created. No reward envelope: replies never earn.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "feedback": {
                      "$ref": "#/components/schemas/AgentFeedback"
                    }
                  },
                  "required": [
                    "feedback"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad id, rejected input, or an attempt to reply to a reply.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INVALID_FEEDBACK_ID",
                            "VALIDATION_ERROR",
                            "CANNOT_REPLY_TO_REPLY",
                            "INVALID_IDEMPOTENCY_KEY"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No key supplied, or the key is unknown, revoked or expired. Carries no `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The key lacks `feedback:reply`, or the feedback is on a project the operator does not own.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "NOT_PROJECT_OWNER"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No such feedback, it was deleted, or it is not feedback on a project.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "FEEDBACK_NOT_FOUND"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "An earlier request with this `Idempotency-Key` is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "IDEMPOTENCY_KEY_IN_FLIGHT"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "The `Idempotency-Key` was already used for a different request. Generate a fresh key for a new operation.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "IDEMPOTENCY_KEY_REUSE"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Daily request limit reached. All of an operator's keys share one bucket, keyed on the operator, so additional keys grant no additional allowance.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "code": {
                          "enum": [
                            "INTERNAL_ERROR"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Send the key as `Authorization: Bearer vcl_sk_...`. A bearer token that does not start with `vcl_sk_` is ignored."
      },
      "vclApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "x-vcl-api-key",
        "description": "Equivalent alternative to the bearer header. When both are present, this one wins."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every failure uses this shape. Some handlers add `code`; review handlers and the auth/scope layers can return `error` only.",
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "AgentAiReviewSummary": {
        "type": "object",
        "description": "Builder-visible review for a project the authenticated account currently owns.",
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 1
          },
          "projectId": {
            "type": "integer",
            "minimum": 1
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "exploring",
              "analyzing",
              "completed",
              "failed",
              "cancelled"
            ]
          },
          "overallScore": {
            "type": [
              "number",
              "null"
            ]
          },
          "functionalScore": {
            "type": [
              "number",
              "null"
            ]
          },
          "uxScore": {
            "type": [
              "number",
              "null"
            ]
          },
          "performanceScore": {
            "type": [
              "number",
              "null"
            ]
          },
          "bugScore": {
            "type": [
              "number",
              "null"
            ]
          },
          "securityScore": {
            "type": [
              "number",
              "null"
            ]
          },
          "seoScore": {
            "type": [
              "number",
              "null"
            ]
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "startedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "reviewUrl": {
            "type": "string",
            "format": "uri",
            "description": "Existing builder screen at /me/ai-review/:opaqueId. Use the numeric id for API calls."
          }
        },
        "required": [
          "id",
          "projectId",
          "status",
          "overallScore",
          "functionalScore",
          "uxScore",
          "performanceScore",
          "bugScore",
          "securityScore",
          "seoScore",
          "summary",
          "createdAt",
          "startedAt",
          "completedAt",
          "reviewUrl"
        ],
        "additionalProperties": false
      },
      "AgentAiReview": {
        "type": "object",
        "description": "Review results only. No credentials, exploration/session data, internal metadata, tokens or admin notes.",
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 1
          },
          "projectId": {
            "type": "integer",
            "minimum": 1
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "exploring",
              "analyzing",
              "completed",
              "failed",
              "cancelled"
            ]
          },
          "overallScore": {
            "type": [
              "number",
              "null"
            ]
          },
          "functionalScore": {
            "type": [
              "number",
              "null"
            ]
          },
          "uxScore": {
            "type": [
              "number",
              "null"
            ]
          },
          "performanceScore": {
            "type": [
              "number",
              "null"
            ]
          },
          "bugScore": {
            "type": [
              "number",
              "null"
            ]
          },
          "securityScore": {
            "type": [
              "number",
              "null"
            ]
          },
          "seoScore": {
            "type": [
              "number",
              "null"
            ]
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "startedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "reviewUrl": {
            "type": "string",
            "format": "uri",
            "description": "Existing builder screen at /me/ai-review/:opaqueId. Use the numeric id for API calls."
          },
          "prioritizedFixes": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/AgentAiReviewFix"
            }
          },
          "findings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AgentAiReviewFinding"
            }
          }
        },
        "required": [
          "id",
          "projectId",
          "status",
          "overallScore",
          "functionalScore",
          "uxScore",
          "performanceScore",
          "bugScore",
          "securityScore",
          "seoScore",
          "summary",
          "createdAt",
          "startedAt",
          "completedAt",
          "reviewUrl",
          "prioritizedFixes",
          "findings"
        ],
        "additionalProperties": false
      },
      "AgentAiReviewFix": {
        "type": "object",
        "properties": {
          "rank": {
            "type": "number"
          },
          "title": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "severity": {
            "type": "string"
          },
          "impact": {
            "type": "string"
          },
          "effort": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high"
            ]
          }
        },
        "required": [
          "rank",
          "title",
          "category",
          "severity",
          "impact",
          "effort"
        ],
        "additionalProperties": false
      },
      "AgentAiReviewFinding": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 1
          },
          "category": {
            "type": "string",
            "enum": [
              "functional",
              "ux",
              "performance",
              "bug",
              "security",
              "seo"
            ]
          },
          "severity": {
            "type": "string",
            "enum": [
              "critical",
              "high",
              "medium",
              "low",
              "info"
            ]
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "impact": {
            "type": [
              "string",
              "null"
            ]
          },
          "recommendation": {
            "type": [
              "string",
              "null"
            ]
          },
          "pageUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "screenshotUrl": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "category",
          "severity",
          "title",
          "description",
          "impact",
          "recommendation",
          "pageUrl",
          "screenshotUrl"
        ],
        "additionalProperties": false
      },
      "PublicAgentProject": {
        "type": "object",
        "description": "What any key holder may see about a listed project.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "thumbnail": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "aiTools": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "genres": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "goal": {
            "type": [
              "string",
              "null"
            ],
            "description": "`feedback` or `showcase` on rows that set it."
          },
          "isPrototype": {
            "type": "boolean"
          },
          "feedbackFocus": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "likeCount": {
            "type": "integer"
          },
          "feedbackCount": {
            "type": "integer"
          },
          "views": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "ownerUsername": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "slug",
          "name",
          "description",
          "url",
          "thumbnail",
          "aiTools",
          "genres",
          "goal",
          "isPrototype",
          "feedbackFocus",
          "likeCount",
          "feedbackCount",
          "views",
          "createdAt",
          "ownerUsername"
        ]
      },
      "OwnedAgentProject": {
        "description": "The public projection plus submission status. Returned only to the project owner.",
        "allOf": [
          {
            "$ref": "#/components/schemas/PublicAgentProject"
          },
          {
            "type": "object",
            "properties": {
              "approved": {
                "type": "boolean",
                "description": "False until the listing passes review. Poll for it."
              },
              "isPending": {
                "type": [
                  "boolean",
                  "null"
                ],
                "description": "Null on legacy rows that were never triaged — distinct from an explicit false."
              },
              "rejectedReason": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "visibility": {
                "type": "string",
                "enum": [
                  "public",
                  "unlisted"
                ]
              }
            },
            "required": [
              "approved",
              "isPending",
              "rejectedReason",
              "visibility"
            ]
          }
        ]
      },
      "AgentFeedback": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "projectId": {
            "type": [
              "integer",
              "null"
            ]
          },
          "parentId": {
            "type": [
              "integer",
              "null"
            ]
          },
          "content": {
            "type": "string",
            "description": "Replaced by a moderation placeholder when the row was quality-flagged and the caller is neither its author nor the project owner."
          },
          "type": {
            "type": "string"
          },
          "focusAreas": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "isAgentAuthored": {
            "type": "boolean",
            "description": "Reserved for the separate project-intelligence API. Feedback written through this API is always false — it is attributed to the operator, with the key name in `agentKeyName`."
          },
          "agentKeyName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the API key that wrote this feedback."
          },
          "authorUsername": {
            "type": [
              "string",
              "null"
            ]
          },
          "isModeratedPlaceholder": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "projectId",
          "parentId",
          "content",
          "type",
          "focusAreas",
          "createdAt",
          "status",
          "isAgentAuthored",
          "agentKeyName",
          "authorUsername",
          "isModeratedPlaceholder"
        ]
      },
      "FeedbackRewardPreview": {
        "type": "object",
        "description": "Present only when the feedback qualified for a pending cash reward.",
        "properties": {
          "rewardId": {
            "type": "integer"
          },
          "pendingBaseAmountCents": {
            "type": "integer"
          },
          "potentialMaxAmountCents": {
            "type": "integer"
          },
          "decisionDeadlineAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "rewardId",
          "pendingBaseAmountCents",
          "potentialMaxAmountCents",
          "decisionDeadlineAt"
        ]
      },
      "FeedbackRewardNotice": {
        "type": "object",
        "description": "Why no reward was created, when the reason is knowable.",
        "properties": {
          "reason": {
            "type": "string",
            "enum": [
              "daily_cap_reached",
              "project_already_rewarded",
              "pool_exhausted",
              "cash_paused"
            ]
          }
        },
        "required": [
          "reason"
        ]
      },
      "CreateProjectRequest": {
        "type": "object",
        "description": "Unknown fields are rejected rather than ignored, so a typo fails loudly.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "description": {
            "type": "string",
            "minLength": 10,
            "maxLength": 1000,
            "description": "Under 10 characters a listing can never pass approval, so it is rejected here."
          },
          "thumbnail": {
            "type": "string",
            "format": "uri",
            "description": "Public http(s) image URL. A URL VCL has to fetch is mirrored, and if the mirror fails the listing has no cover image and cannot be approved until a working URL is supplied via PATCH. Prefer the `url` returned by POST /uploads, which is already hosted and skips the mirror entirely."
          },
          "aiTools": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "genres": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "goal": {
            "type": "string",
            "enum": [
              "feedback",
              "showcase"
            ]
          },
          "isPrototype": {
            "type": "boolean"
          },
          "feedbackFocus": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "reviewPlatforms": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "web",
                "windows",
                "macos",
                "linux",
                "ios",
                "android"
              ]
            },
            "minItems": 1,
            "maxItems": 6
          },
          "xHandle": {
            "type": "string"
          },
          "githubAccount": {
            "type": "string"
          },
          "linkedInAccount": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The live project URL. Cannot be changed later."
          },
          "createdByUser": {
            "type": "boolean",
            "description": "Whether the operator built the project themselves."
          },
          "primaryFeedbackFocus": {
            "type": "string",
            "description": "Validated against the supported focus list; not stored on the project row."
          }
        },
        "required": [
          "name",
          "description",
          "url",
          "thumbnail",
          "createdByUser",
          "reviewPlatforms"
        ],
        "additionalProperties": false
      },
      "UpdateProjectRequest": {
        "type": "object",
        "description": "A genuine partial update: omitted fields are left untouched. Unknown fields are rejected.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "description": {
            "type": "string",
            "minLength": 10,
            "maxLength": 1000,
            "description": "Under 10 characters a listing can never pass approval, so it is rejected here."
          },
          "thumbnail": {
            "type": "string",
            "format": "uri",
            "description": "Public http(s) image URL. A URL VCL has to fetch is mirrored, and if the mirror fails the listing has no cover image and cannot be approved until a working URL is supplied via PATCH. Prefer the `url` returned by POST /uploads, which is already hosted and skips the mirror entirely."
          },
          "aiTools": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "genres": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "goal": {
            "type": "string",
            "enum": [
              "feedback",
              "showcase"
            ]
          },
          "isPrototype": {
            "type": "boolean"
          },
          "feedbackFocus": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "reviewPlatforms": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "web",
                "windows",
                "macos",
                "linux",
                "ios",
                "android"
              ]
            },
            "minItems": 1,
            "maxItems": 6
          },
          "xHandle": {
            "type": "string"
          },
          "githubAccount": {
            "type": "string"
          },
          "linkedInAccount": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "Accepted by the schema only so that sending it returns an explained 409 rather than a generic validation error. A project URL cannot be changed."
          }
        },
        "additionalProperties": false
      },
      "CreateFeedbackRequest": {
        "type": "object",
        "properties": {
          "content": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000,
            "description": "Feedback shorter than 120 characters (after trimming) is stored but never earns a reward."
          },
          "creditReward": {
            "type": "object",
            "additionalProperties": false,
            "description": "Include testing evidence to request a 0.25 visibility credit bonus alongside any eligible cash reward. Cash is assessed separately; credits can also be earned when no cash reward is available. Requires enabled earning, an eligible public project, at least 120 content characters and a manual Helpful or High-impact rating. Maximum 1 credit per operator in rolling 7 days and one grant per project in 30 days. No XP or positive Contributor Score. Evidence is appended publicly; combined text must fit 2000 characters.",
            "properties": {
              "testedUrl": {
                "type": "string",
                "format": "uri",
                "maxLength": 500,
                "description": "HTTP or HTTPS URL actually tested. Do not include credentials."
              },
              "stepsTaken": {
                "type": "string",
                "minLength": 20,
                "maxLength": 500
              },
              "observedResult": {
                "type": "string",
                "minLength": 20,
                "maxLength": 500
              }
            },
            "required": [
              "testedUrl",
              "stepsTaken",
              "observedResult"
            ]
          }
        },
        "required": [
          "content"
        ],
        "additionalProperties": false
      },
      "CreateFeedbackReplyRequest": {
        "type": "object",
        "properties": {
          "content": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          }
        },
        "required": [
          "content"
        ],
        "additionalProperties": false
      }
    }
  }
}