{
  "openapi": "3.1.0",
  "info": {
    "title": "Mural",
    "version": "1.0.0",
    "description": "Mural — anúncios de software e serviço."
  },
  "servers": [
    {
      "url": "https://classificado.app.br"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Guest mr_…, sessão sess_… ou ADMIN_TOKEN."
      }
    }
  },
  "paths": {
    "/api/": {
      "get": {
        "summary": "Índice auto-descrito.",
        "description": "Devolve: { name, endpoints[] }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ name, endpoints[] }"
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "summary": "Liveness.",
        "description": "Devolve: { ok, app, build, ts }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ ok, app, build, ts }"
          }
        }
      }
    },
    "/api/guest": {
      "post": {
        "summary": "Cria guest `mr_…`.",
        "description": "Devolve: { token }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ token }"
          }
        }
      }
    },
    "/api/listings": {
      "get": {
        "summary": "Mosaico público (só live). q, kind, category, sort=recent|likes|visits, limit, offset.",
        "description": "Devolve: { items[], limit, offset, next_offset }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ items[], limit, offset, next_offset }"
          }
        }
      },
      "post": {
        "summary": "Anuncia. Humano com sessão: grátis, 1/dia, máx. 3 pending. Guest: 401. Agente sem sessão: x402 $0.10.",
        "description": "Devolve: { ok, id, status: 'pending' } | 402 accepts[]",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "kind": "software",
                "category": "ferramentas",
                "name": "Nome",
                "tagline": "Uma linha",
                "body": "Texto",
                "url": "https://exemplo.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ ok, id, status: 'pending' } | 402 accepts[]"
          }
        }
      }
    },
    "/api/listings/{id}": {
      "get": {
        "summary": "Ficha live. Dono vê a própria pending/hidden.",
        "description": "Devolve: { id, name, url, likes, visits, api, go }",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ id, name, url, likes, visits, api, go }"
          }
        }
      },
      "patch": {
        "summary": "Edita o próprio. Mudar a URL volta para pending.",
        "description": "Devolve: { ok, id, status }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "tagline": "…"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ ok, id, status }"
          }
        }
      }
    },
    "/api/go/{id}": {
      "get": {
        "summary": "302 para a URL do anúncio. Conta 1 visita/dono/dia. Header X-Visit-Counted.",
        "description": "Devolve: 302 Location",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "302 Location"
          }
        }
      }
    },
    "/api/listings/{id}/comments": {
      "get": {
        "summary": "Comentários do anúncio live.",
        "description": "Devolve: { items[], total }",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ items[], total }"
          }
        }
      },
      "post": {
        "summary": "Escreve comentário (20/h).",
        "description": "Devolve: { ok, id, body }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "body": "texto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ ok, id, body }"
          }
        }
      }
    },
    "/api/listings/{id}/like": {
      "post": {
        "summary": "Liga o like. Idempotente.",
        "description": "Devolve: { ok, liked, likes }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ ok, liked, likes }"
          }
        }
      },
      "delete": {
        "summary": "Tira o like e devolve o ponto.",
        "description": "Devolve: { ok, liked, likes }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ ok, liked, likes }"
          }
        }
      }
    },
    "/api/comments/{id}": {
      "delete": {
        "summary": "Apaga o próprio comentário. Alheio = 404.",
        "description": "Devolve: { ok, id }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ ok, id }"
          }
        }
      }
    },
    "/api/me/listings": {
      "get": {
        "summary": "Anúncios do dono, qualquer status.",
        "description": "Devolve: { items[] }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ items[] }"
          }
        }
      }
    },
    "/api/admin/listings": {
      "get": {
        "summary": "Fila. Default status=pending. ADMIN_TOKEN ou sessão do ADMIN_EMAIL.",
        "description": "Devolve: { items[] }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ items[] }"
          }
        }
      }
    },
    "/api/admin/listings/{id}": {
      "post": {
        "summary": "approve | hide | block.",
        "description": "Devolve: { ok, status }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "action": "approve"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ ok, status }"
          }
        }
      }
    },
    "/api/billing": {
      "get": {
        "summary": "x402 live e preços (contact_agent_usd, listing_agent_usd).",
        "description": "Devolve: { provider, network, homolog, prices }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ provider, network, homolog, prices }"
          }
        }
      }
    },
    "/api/contact": {
      "post": {
        "summary": "Suporte: Turnstile (humano) ou x402 $0.10 (agente).",
        "description": "Devolve: { ok } | 402 accepts[]",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "name": "…",
                "email": "a@example.com",
                "message": "…",
                "form_ts": 0
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ ok } | 402 accepts[]"
          }
        }
      }
    },
    "/api/me": {
      "get": {
        "summary": "Usuário da sessão + admin.",
        "description": "Devolve: { user, admin }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ user, admin }"
          }
        }
      }
    },
    "/api/auth/start": {
      "post": {
        "summary": "Envia OTP.",
        "description": "Devolve: { ok }",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "email": "voce@exemplo.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ ok }"
          }
        }
      }
    },
    "/api/auth/verify": {
      "post": {
        "summary": "Troca OTP por sess_…",
        "description": "Devolve: { ok, token, user }",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "email": "voce@exemplo.com",
                "code": "123456"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ ok, token, user }"
          }
        }
      }
    },
    "/api/auth/claim": {
      "post": {
        "summary": "Amarra guest à conta.",
        "description": "Devolve: { ok, claimed }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "guest_token": "mr_…"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ ok, claimed }"
          }
        }
      }
    },
    "/api/auth/logout": {
      "post": {
        "summary": "Invalida sessão.",
        "description": "Devolve: { ok }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ ok }"
          }
        }
      }
    },
    "/api/visit": {
      "post": {
        "summary": "Ping da UI. Smoke não conta.",
        "description": "Devolve: { ok, counted }",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "p": "/"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ ok, counted }"
          }
        }
      }
    },
    "/api/metrics": {
      "get": {
        "summary": "Visitas/uso. METRICS_TOKEN libera payments.",
        "description": "Devolve: { app, today_visits, usage, accounts }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ app, today_visits, usage, accounts }"
          }
        }
      }
    }
  }
}