{
  "openapi": "3.1.0",
  "info": {
    "title": "PescaConnect API",
    "version": "1.0.0",
    "description": "API pública de leitura do catálogo de pescarias guiadas da PescaConnect. Não exige autenticação e devolve apenas dados que já aparecem na vitrine do site. Limite de 60 requisições por minuto por endereço IP. As respostas são cacheadas por 5 minutos no CDN, então consulta repetida é barata. Preços em reais, referentes à pescaria inteira e não por pessoa. Versionamento no caminho (/api/v1/). Mudança incompatível entra em /api/v2/, nunca em /api/v1/. Depreciação é sinalizada com os headers Deprecation e Sunset (RFC 8594) por pelo menos 180 dias antes do desligamento, e anunciada em /docs#versionamento.",
    "contact": {
      "email": "contato@pescaconnect.com",
      "url": "https://pescaconnect.com/contato"
    }
  },
  "servers": [
    {
      "url": "https://pescaconnect.com"
    }
  ],
  "paths": {
    "/api/v1/fishing-trips": {
      "get": {
        "operationId": "listFishingTrips",
        "summary": "Lista pescarias guiadas disponíveis",
        "description": "Devolve pescarias publicadas, com barco aprovado e ao menos uma foto. Use os filtros de cidade, estado, modalidade e preço para estreitar o resultado. A resposta é paginada por limit e offset, e o preço é da pescaria inteira, não por pessoa.",
        "parameters": [
          {
            "name": "city",
            "in": "query",
            "required": false,
            "description": "Nome da cidade. Casamento parcial, sem distinção de maiúscula.",
            "schema": {
              "type": "string",
              "example": "Florianópolis"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "description": "Sigla da UF, duas letras.",
            "schema": {
              "type": "string",
              "example": "SC"
            }
          },
          {
            "name": "modality",
            "in": "query",
            "required": false,
            "description": "Modalidade da pescaria: oceanica, costeira, aguas-abrigadas, represas ou rios.",
            "schema": {
              "type": "string",
              "example": "oceanica"
            }
          },
          {
            "name": "min_price",
            "in": "query",
            "required": false,
            "description": "Preço mínimo da pescaria inteira, em reais.",
            "schema": {
              "type": "number",
              "example": 400
            }
          },
          {
            "name": "max_price",
            "in": "query",
            "required": false,
            "description": "Preço máximo da pescaria inteira, em reais.",
            "schema": {
              "type": "number",
              "example": 2000
            }
          },
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "Data desejada no formato YYYY-MM-DD. Mantém apenas pescarias com essa data disponível.",
            "schema": {
              "type": "string",
              "example": "2026-12-24"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Itens por página. Padrão 20, máximo 100.",
            "schema": {
              "type": "integer",
              "example": 20
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Itens a pular. Use junto de limit para paginar.",
            "schema": {
              "type": "integer",
              "example": 40
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FishingTrip"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Parâmetro inválido. O campo code diz qual.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Acima de 60 requisições por minuto. Respeite o header Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A fonte de dados não respondeu. Tente de novo em segundos — não é ausência de resultado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/fishing-trips/{id}": {
      "get": {
        "operationId": "getFishingTrip",
        "summary": "Detalha uma pescaria",
        "description": "Devolve uma pescaria pelo id obtido em listFishingTrips. Responde 404 quando a pescaria não está publicada — ela pode ter sido despublicada pelo barqueiro.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID da pescaria.",
            "schema": {
              "type": "string",
              "example": "3f6c8a1e-5b2d-4c7a-9e8f-1a2b3c4d5e6f"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FishingTrip"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Parâmetro inválido. O campo code diz qual.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Nenhuma pescaria publicada com esse id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Acima de 60 requisições por minuto. Respeite o header Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A fonte de dados não respondeu. Tente de novo em segundos — não é ausência de resultado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities": {
      "get": {
        "operationId": "listCities",
        "summary": "Lista cidades com pescaria disponível",
        "description": "Devolve as cidades que têm pescaria publicada, com a contagem de cada uma e a URL da página da cidade. É o ponto de partida natural: descubra onde há pescaria antes de filtrar qual.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/City"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Parâmetro inválido. O campo code diz qual.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Acima de 60 requisições por minuto. Respeite o header Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A fonte de dados não respondeu. Tente de novo em segundos — não é ausência de resultado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/availability": {
      "get": {
        "operationId": "getAvailability",
        "summary": "Consulta datas disponíveis de uma pescaria",
        "description": "Devolve as datas futuras em que a pescaria tem vaga aberta, no formato YYYY-MM-DD, incluindo o dia de hoje. Lista vazia significa agenda sem data aberta, não erro.",
        "parameters": [
          {
            "name": "trip_id",
            "in": "query",
            "required": false,
            "description": "UUID da pescaria.",
            "schema": {
              "type": "string",
              "example": "3f6c8a1e-5b2d-4c7a-9e8f-1a2b3c4d5e6f"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Availability"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Parâmetro inválido. O campo code diz qual.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Acima de 60 requisições por minuto. Respeite o header Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A fonte de dados não respondeu. Tente de novo em segundos — não é ausência de resultado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "FishingTrip": {
        "type": "object",
        "description": "Uma pescaria guiada publicada. Não inclui dados do guia nem coordenadas do ponto de embarque.",
        "properties": {
          "id": {
            "type": "string",
            "description": "UUID da pescaria."
          },
          "name": {
            "type": "string",
            "description": "Nome anunciado pelo barqueiro."
          },
          "modality": {
            "type": "string",
            "description": "Modalidade: oceânica, costeira, águas abrigadas, represas ou rios."
          },
          "price": {
            "type": "number",
            "description": "Preço da pescaria inteira, em reais."
          },
          "duration": {
            "type": "string",
            "description": "Duração como anunciada, por exemplo \"10h\"."
          },
          "duration_hours": {
            "type": "number",
            "description": "Duração em horas, numérica."
          },
          "capacity": {
            "type": "integer",
            "description": "Número máximo de pescadores."
          },
          "description": {
            "type": "string",
            "description": "Descrição escrita pelo barqueiro."
          },
          "photos": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "URLs das fotos."
          },
          "city": {
            "type": "string",
            "description": "Cidade de saída."
          },
          "state": {
            "type": "string",
            "description": "Sigla da UF."
          },
          "target_species": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Espécies-alvo informadas."
          },
          "amenities": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "O que está incluso."
          },
          "start_time": {
            "type": "string",
            "description": "Horário de saída, por exemplo \"06:00\"."
          },
          "rating": {
            "type": "number",
            "description": "Nota média. Presente SOMENTE quando reviews_count é maior que zero — ausência significa que ninguém avaliou ainda, não nota baixa."
          },
          "reviews_count": {
            "type": "integer",
            "description": "Quantidade de avaliações de quem reservou e pescou."
          }
        },
        "required": [
          "id",
          "name",
          "price",
          "city",
          "state"
        ]
      },
      "City": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string",
            "description": "Nome da cidade."
          },
          "state": {
            "type": "string",
            "description": "Sigla da UF."
          },
          "slug": {
            "type": "string",
            "description": "Slug usado na URL da página da cidade."
          },
          "fishing_trips": {
            "type": "integer",
            "description": "Quantidade de pescarias publicadas na cidade."
          },
          "url": {
            "type": "string",
            "description": "URL da página da cidade no site."
          }
        },
        "required": [
          "city",
          "state",
          "slug",
          "fishing_trips",
          "url"
        ]
      },
      "Availability": {
        "type": "object",
        "properties": {
          "trip_id": {
            "type": "string",
            "description": "UUID da pescaria consultada."
          },
          "available_dates": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Datas YYYY-MM-DD com vaga aberta, de hoje em diante. Lista vazia é agenda sem data aberta, não erro."
          }
        },
        "required": [
          "trip_id",
          "available_dates"
        ]
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer",
            "description": "Itens por página nesta resposta."
          },
          "offset": {
            "type": "integer",
            "description": "Itens pulados nesta resposta."
          },
          "has_next": {
            "type": "boolean",
            "description": "Se existe próxima página. Some offset e limit para buscá-la."
          }
        },
        "required": [
          "limit",
          "offset",
          "has_next"
        ]
      },
      "Error": {
        "type": "object",
        "description": "Forma única de erro desta API.",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Identificador estável do erro. Decida pelo code, não pela mensagem.",
                "enum": [
                  "invalid_parameter",
                  "invalid_limit",
                  "invalid_offset",
                  "invalid_price",
                  "invalid_date",
                  "invalid_state",
                  "invalid_city",
                  "invalid_modality",
                  "invalid_id",
                  "trip_not_found",
                  "method_not_allowed",
                  "rate_limited",
                  "source_unavailable"
                ]
              },
              "message": {
                "type": "string",
                "description": "O que aconteceu, em português."
              },
              "hint": {
                "type": "string",
                "description": "O que fazer para corrigir."
              },
              "docs": {
                "type": "string",
                "description": "URL da seção de /docs que explica este code."
              }
            },
            "required": [
              "code",
              "message",
              "hint",
              "docs"
            ]
          }
        },
        "required": [
          "error"
        ]
      }
    }
  }
}
