Sites API - Documentação Pública
📖 Endpoints públicos para integração com sites externos, WordPress, e aplicações de terceiros. Estes endpoints não requerem autenticação e são otimizados para consumo por frontends públicos.
💡 Usando Next.js ou React? O
@garagem-ai/site-sdkoferece fetchers tipados (getListings,getListing,getSimilarListings, CMS), caching nativo do App Router e componentes prontos em cima desta API. Veja sdk.garagem.site/docs para o quickstart.
Visão Geral
Seção intitulada “Visão Geral”A Sites API permite que aplicações externas (como WordPress, sites custom, ou mobile apps) consumam dados de imóveis do Garagem CRM. Os endpoints são:
- CORS habilitado para qualquer origem
- Cache otimizado com headers HTTP para CDNs
- Dados sanitizados (informações sensíveis removidas)
Base URL
Seção intitulada “Base URL”https://app.garagem.ai/api/sitesObter o Site ID
Seção intitulada “Obter o Site ID”Para usar a Sites API, você precisa do Site ID do seu site configurado no Garagem. Existem duas formas de obtê-lo:
Opção 1: Via Dashboard (Recomendado)
Seção intitulada “Opção 1: Via Dashboard (Recomendado)”- Acesse Configurações → Canais → Sites no Garagem
- Clique no site desejado
- O Site ID aparece na URL:
https://app.garagem.ai/configuracoes/sites/{SITE_ID} - Ou copie do campo “ID do Site” na página de configuração
Opção 2: Via API (usando domínio)
Seção intitulada “Opção 2: Via API (usando domínio)”Se você já tem um domínio configurado, pode usar o endpoint /resolve para obter o Site ID:
curl "https://cdn.fndn.ai/site/api/sites/resolve?domain=imoveis.seusite.com.br"A resposta incluirá o id do site que você pode usar nos demais endpoints.
📍 Endpoints
Seção intitulada “📍 Endpoints”1. Resolver Site
Seção intitulada “1. Resolver Site”Obtém a configuração completa de um site pelo domínio ou ID.
GET /api/sites/resolve?domain={domain}GET /api/sites/resolve?siteId={siteId}Parâmetros
Seção intitulada “Parâmetros”| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
domain | string | * | Domínio do site (ex: imoveis.exemplo.com) |
siteId | string | * | UUID do site |
* Pelo menos um dos parâmetros é obrigatório.
Resposta de Sucesso (200)
Seção intitulada “Resposta de Sucesso (200)”{ "id": "uuid-do-site", "name": "Nome do Site", "domain": "imoveis.exemplo.com", "configuration": { "puck_data": { ... }, "siteConfiguration": { "brand": { "logoUrl": "https://...", "name": "Imobiliária Exemplo", "primaryColor": "#FF5500" }, "menu": [ ... ], "footer": { ... } }, "otherConfigs": null }, "tenantId": "uuid-do-tenant", "listingCriteria": { "propertyTypes": ["apartment", "house"], "locations": ["São Paulo"] }, "tenantConfig": { "propertyFeatures": [ ... ], "buildingFeatures": [ ... ], "primaryMarketEnabled": false, "propertyTypes": [ { "id": "apartment", "label": "Apartamento" }, { "id": "house", "label": "Casa" } ], "supportedLanguages": ["pt-BR", "en"] }, "metaPixel": { "pixelId": "123456789", "events": { "viewContent": true, "search": true, "lead": true } }, "cachedAt": 1706889600000}tenantConfig.supportedLanguages
Seção intitulada “tenantConfig.supportedLanguages”Idiomas habilitados para a UI pública do site (rótulos, formatação). Valores possíveis: "pt-BR", "en", "es".
- Sempre inclui
"pt-BR"(idioma padrão; URLs sem prefixo de locale). - Locales extras são opt-in na configuração do tenant (Tipos → idiomas do anúncio).
- Quando
"en"ou"es"estão presentes, o site aceita rotas com prefixo (/en/...,/es/...) e renderiza o chrome do sistema nesse idioma. - Para título e descrição do anúncio em outros idiomas, veja Título e descrição por idioma abaixo.
tenantConfig.primaryMarketEnabled
Seção intitulada “tenantConfig.primaryMarketEnabled”Indica se o tenant tem mercado primário (lançamentos) habilitado no CRM.
| Valor | Comportamento |
|---|---|
true | Sites Garagem exibem fluxos de lançamento (tipologias, unidades, empreendimento) |
false | UI de lançamento oculta no site; os endpoints públicos abaixo continuam existindo |
Os critérios do site (listingCriteria) podem restringir o catálogo a um tipo de mercado. Quando marketType está configurado como primary ou secondary no painel do site, o filtro é aplicado automaticamente em POST /search (mesma lógica dos critérios de tipo de imóvel e localização).
Para tipologias, unidades e empreendimento, veja Mercado primário (lançamentos) abaixo.
| Status | Erro | Descrição |
|---|---|---|
| 400 | Invalid parameter | Parâmetro domain ou siteId ausente |
| 404 | Site not found | Site não encontrado ou inativo |
| 404 | Site not published | Site não publicado |
Exemplo cURL
Seção intitulada “Exemplo cURL”curl "https://cdn.fndn.ai/site/api/sites/resolve?domain=imoveis.exemplo.com"Título e descrição por idioma
Seção intitulada “Título e descrição por idioma”O português é a fonte canônica de cada anúncio: title e description nos endpoints públicos sempre trazem o texto em português. Traduções em inglês e espanhol vêm em campos separados no documento de busca — a API não reescreve title/description com base em ?locale= ou cabeçalho; quem consome a API escolhe o idioma no cliente.
Códigos de locale
Seção intitulada “Códigos de locale”| Uso | Código | Onde aparece |
|---|---|---|
| Português (fonte) | pt-BR | title, description |
| Inglês | en | title_en, description_en |
| Espanhol | es | title_es, description_es |
Use
enees— nãoen-US/es-ES. O localept-BRnunca aparece dentro de um objetotranslationsna API REST; fica só nas colunas principais.
Campos na busca e no detalhe
Seção intitulada “Campos na busca e no detalhe”Nos hits de POST /api/sites/{siteId}/search, no GET /api/sites/{siteId}/property/{id} e no feed por canal, cada documento pode incluir:
| Campo | Descrição |
|---|---|
title / description | Texto em português |
title_en / description_en | Tradução em inglês (string vazia se ausente) |
title_es / description_es | Tradução em espanhol (string vazia se ausente) |
Esses campos não são removidos pela sanitização pública (diferente de contatos e endereço exato).
Como resolver o texto para exibir
Seção intitulada “Como resolver o texto para exibir”Leia tenantConfig.supportedLanguages no /resolve para saber quais idiomas o tenant habilitou. Para cada imóvel, aplique fallback para português quando a tradução estiver em branco:
function resolveListingCopy(doc, locale) { if (locale === "pt-BR" || locale === "pt") { return { title: doc.title, description: doc.description ?? "" }; } const titleKey = locale === "en" ? "title_en" : "title_es"; const descKey = locale === "en" ? "description_en" : "description_es"; const translatedTitle = doc[titleKey]?.trim(); const translatedDesc = doc[descKey]?.trim(); return { title: translatedTitle || doc.title, description: translatedDesc || doc.description || "", };}Busca textual em inglês ou espanhol
Seção intitulada “Busca textual em inglês ou espanhol”O query_by padrão do site prioriza campos em português (searchText, title, listingCode, …). Uma busca com q=apartment não encontra só title_en a menos que você inclua os campos traduzidos:
{ "q": "apartment", "query_by": "title,title_en,description,description_en"}Para espanhol, use title_es e description_es da mesma forma.
Escrever traduções (API REST)
Seção intitulada “Escrever traduções (API REST)”Parceiros que alimentam copy traduzido usam listings[].translations em POST/PATCH /api/v1/real-estate/properties — veja Anúncios — Traduções. O tenant precisa ter o idioma habilitado em Configurações → Estrutura → Tipos → idiomas do anúncio. Após salvar, o índice de busca expõe os campos title_* / description_* acima.
Site SDK
Seção intitulada “Site SDK”Os fetchers amigáveis getListings e getListing do @garagem-ai/site-sdk mapeiam apenas title e description em português. Para ler title_en, title_es, etc., use searchListings (resposta bruta do Typesense) ou chame diretamente POST /api/sites/{siteId}/search e GET /api/sites/{siteId}/property/{id}.
2. Configuração Pública
Seção intitulada “2. Configuração Pública”Obtém a configuração pública de um site específico pelo ID.
GET /api/sites/{siteId}/public-configNota: O endpoint
/resolveé preferível pois combina lookup + config em uma única chamada.
Resposta
Seção intitulada “Resposta”Mesma estrutura do endpoint /resolve.
3. Busca de Imóveis
Seção intitulada “3. Busca de Imóveis”Proxy para busca no Typesense com filtros do site automaticamente aplicados.
POST /api/sites/{siteId}/searchContent-Type: application/jsonBody (JSON)
Seção intitulada “Body (JSON)”Aceita todos os parâmetros de busca do Typesense:
{ "q": "apartamento copacabana", "query_by": "title,description,location_neighborhood,location_city", "filter_by": "bedrooms:>=2 && price:<1000000", "sort_by": "price:asc", "per_page": 20, "page": 1, "facet_by": "propertyType,bedrooms,location_neighborhood"}Parâmetros de Busca
Seção intitulada “Parâmetros de Busca”| Parâmetro | Tipo | Descrição |
|---|---|---|
q | string | Termo de busca (use * para todos) |
query_by | string | Campos para buscar (separados por vírgula). Padrão do site é PT; inclua title_en/description_en (ou title_es/description_es) para buscar copy traduzida — veja Título e descrição por idioma. |
filter_by | string | Filtros Typesense |
sort_by | string | Ordenação (ex: price:asc, createdAt:desc) |
per_page | number | Resultados por página (max: 250) |
page | number | Página atual |
facet_by | string | Campos para facets/agregações |
Filtros Automáticos
Seção intitulada “Filtros Automáticos”O endpoint automaticamente aplica:
visibility:=public- Apenas anúncios públicosstage:=active- Apenas anúncios ativos (ou estágio configurado pelo tenant)- Listing Criteria do Site - Filtros configurados no painel do site (inclui
marketType:=primaryoumarketType:=secondaryquando configurado)
Filtros de mercado primário
Seção intitulada “Filtros de mercado primário”Combine com filter_by na busca Typesense:
| Filtro | Exemplo | Descrição |
|---|---|---|
marketType | marketType:=primary | Só lançamentos (secondary = revenda) |
constructionStatus | constructionStatus:=plant | Status da obra (valor livre ou código do CRM) |
hasAvailableUnits | hasAvailableUnits:=true | Tipologia com unidades disponíveis no estoque |
{ "q": "*", "query_by": "title,description", "filter_by": "marketType:=primary && hasAvailableUnits:=true && price:<800000", "per_page": 12}Em anúncios de mercado primário,
bedrooms,bathrooms,suites,totalAreaeprivateAreano documento de busca vêm da tipologia vinculada (typologyId), não do imóvel base.
Resposta de Sucesso (200)
Seção intitulada “Resposta de Sucesso (200)”{ "found": 150, "hits": [ { "document": { "id": "listing-uuid", "title": "Apartamento 2 quartos em Copacabana", "description": "Lindo apartamento...", "title_en": "2-bedroom apartment in Copacabana", "description_en": "Beautiful apartment...", "title_es": "", "description_es": "", "price": 850000, "priceFormatted": "R$ 850.000", "listingType": "sale", "listingCode": "APT-001", "propertyType": "apartment", "bedrooms": 2, "bathrooms": 1, "parkingSpots": 1, "totalArea": 75, "location_neighborhood": "Copacabana", "location_city": "Rio de Janeiro", "location_state": "RJ", "location_latitude": -22.971, "location_longitude": -43.185, "primaryImageUrl": "https://cdn.../image.jpg", "imageUrls": ["https://cdn.../img1.jpg", "..."], "isPrimaryMarket": false, "marketType": "secondary", "typologyId": null, "buildingId": null }, "highlights": [ { "field": "title", "snippet": "<mark>Apartamento</mark> 2 quartos em <mark>Copacabana</mark>" } ] } ], "facet_counts": [ { "field_name": "bedrooms", "counts": [ { "value": "2", "count": 45 }, { "value": "3", "count": 32 } ] } ], "page": 1, "search_time_ms": 12}Campos de mercado primário na busca
Seção intitulada “Campos de mercado primário na busca”Presentes em cada document quando o anúncio é de lançamento (ou null / ausentes em revenda):
| Campo | Tipo | Descrição |
|---|---|---|
isPrimaryMarket | boolean | true se lançamento |
marketType | string | "primary" ou "secondary" |
typologyId | uuid | null | Tipologia vinculada (pode ser null em lançamentos só com flag) |
buildingId | uuid | null | Empreendimento / condomínio |
typologyName | string | null | Nome da tipologia |
typologyCode | string | null | Código interno da tipologia |
constructionStatus | string | null | Status da obra |
expectedDelivery | string | null | Previsão de entrega (ex.: "Dezembro 2026") |
availableUnits | number | null | Unidades disponíveis na tipologia |
typologyTotalUnits | number | null | Total de unidades da tipologia |
typologyBasePrice | number | null | Preço base da tipologia |
typologyPricePerSqm | number | null | Preço por m² da tipologia |
hasAvailableUnits | boolean | null | true quando availableUnits > 0 |
Exemplo de hit de lançamento:
{ "document": { "id": "listing-uuid", "title": "Tipo A — 2 dormitórios", "price": 650000, "listingType": "sale", "listingCode": "LAN-001", "propertyType": "apartment", "bedrooms": 2, "bathrooms": 2, "totalArea": 68, "isPrimaryMarket": true, "marketType": "primary", "typologyId": "typology-uuid", "buildingId": "building-uuid", "typologyName": "Tipo A - 2 Dormitórios", "constructionStatus": "structure", "expectedDelivery": "Dezembro 2026", "availableUnits": 12, "hasAvailableUnits": true, "location_neighborhood": "Pinheiros", "location_city": "São Paulo", "location_state": "SP" }}Dados Sanitizados
Seção intitulada “Dados Sanitizados”Por segurança, os seguintes campos são removidos dos resultados:
- Informações de contato (telefone, email, WhatsApp)
- Nome e CPF do proprietário/corretor
- Número exato do endereço
- Notas internas
Coordenadas são mascaradas para ~100m de precisão (3 casas decimais).
O que a busca devolve
Seção intitulada “O que a busca devolve”A busca usa a coleção do tenant dono do Site ID. O filtro fixo é visibility:=public e stage igual à etapa ativa desse tenant (o id padrão é active), mais o listingCriteria configurado no canal do site.
Imóvel de outra imobiliária, vindo de rede, não entra nessa consulta. A inclusão de rede só ocorre quando includeNetworkListings está ligado na configuração do site. O padrão é desligado, e a busca pública não tem parâmetro para ligar isso.
Um imóvel de rede que tenha sido copiado para o inventário do tenant e publicado (visibility public, etapa ativa e critério do site) aparece, porque passou a ser anúncio daquele tenant.
Exemplo cURL
Seção intitulada “Exemplo cURL”curl -X POST "https://app.garagem.ai/api/sites/{siteId}/search" \ -H "Content-Type: application/json" \ -d '{ "q": "*", "query_by": "title", "filter_by": "bedrooms:>=2", "per_page": 12 }'4. Detalhes do Imóvel
Seção intitulada “4. Detalhes do Imóvel”Obtém dados completos de um imóvel específico.
GET /api/sites/{siteId}/property/{id}Parâmetros
Seção intitulada “Parâmetros”| Parâmetro | Tipo | Descrição |
|---|---|---|
siteId | string | UUID do site |
id | string | UUID do imóvel ou código do anúncio |
Query Parameters
Seção intitulada “Query Parameters”| Parâmetro | Tipo | Descrição |
|---|---|---|
shortlistId | string | ID da seleção (permite ver imóveis não publicados) |
Resposta de Sucesso (200)
Seção intitulada “Resposta de Sucesso (200)”{ "id": "listing-uuid", "title": "Apartamento 2 quartos em Copacabana", "description": "Descrição completa do imóvel...", "title_en": "2-bedroom apartment in Copacabana", "description_en": "Full property description in English...", "title_es": "", "description_es": "", "price": 850000, "priceFormatted": "R$ 850.000", "listingType": "sale", "listingCode": "APT-001", "propertyType": "apartment", "bedrooms": 2, "bathrooms": 1, "suites": 1, "parkingSpots": 1, "totalArea": 75, "privateArea": 65, "features": ["pool", "gym", "balcony"], "condoFee": 800, "propertyTax": 350, "location_street": "Av. Atlântica", "location_neighborhood": "Copacabana", "location_city": "Rio de Janeiro", "location_state": "RJ", "location_latitude": -22.971, "location_longitude": -43.185, "primaryImageUrl": "https://cdn.../image.jpg", "imageUrls": ["https://cdn.../img1.jpg", "..."], "imageOrigins": ["original", "fal"], "hasProviderStagedPhotos": true, "videoUrl": "https://youtube.com/...", "tourUrl": "https://matterport.com/...", "isPrimaryMarket": true, "marketType": "primary", "typologyId": "typology-uuid", "buildingId": "building-uuid", "typologyName": "Tipo A - 2 Dormitórios", "constructionStatus": "structure", "expectedDelivery": "Dezembro 2026", "availableUnits": 12, "hasAvailableUnits": true}Os mesmos campos de mercado primário na busca aparecem no detalhe. Use
typologyIdebuildingIdpara carregar tipologias e empreendimento nos endpoints 6–10.
imageOrigins(opcional) — alinhado comimageUrls. Proveniência de staging publicado: cada slot é"original", exceto quando existe variante publicada de staging para aqueleimage_ids[i](fal,arquitetodebolso, …). Independente de a URL servida ter sido trocada. Só aparece com fotos;hasProviderStagedPhotosétruese algum slot ≠"original".
| Status | Erro | Descrição |
|---|---|---|
| 404 | Site not found | Site não encontrado |
| 403 | Site not active | Site inativo |
| 404 | Property not found | Imóvel não encontrado/visível |
4.1 Corretor responsável pelo anúncio
Seção intitulada “4.1 Corretor responsável pelo anúncio”O documento de busca e o detalhe do imóvel não trazem nome, foto nem contato do corretor. O cartão é um endpoint separado, público, sem API key:
GET /api/sites/{siteId}/listings/{listingId}/brokerO siteId localiza o tenant. ?tenantId= é opcional: o app do Garagem envia quando já conhece o tenant, para pular a consulta ao diretório. Integradores não precisam dele.
listingId aceita o UUID do anúncio ou o listingCode.
Resposta (200)
Seção intitulada “Resposta (200)”{ "id": "user-uuid", "name": "Ana Souza", "imageUrl": "https://cdn.fndn.ai/...", "phone": "+5511999999999", "creci": "12345-F"}| Campo | Conteúdo |
|---|---|
id | ID do usuário |
name | Nome do usuário. Não há campo separado de nome público. |
imageUrl | URL da foto, ou null |
phone | Telefone da conta do usuário, ou null. Não há campo separado de WhatsApp profissional. O site usa esse telefone como WhatsApp do cartão. |
creci | CRECI, ou null |
Sem corretor, a resposta é 204. Em anúncio de parceria ou de rede, o corpo é { "suppressed": true }: não renderizar cartão pessoal.
O responsável é property.assignedToId. Se estiver vazio, cai em quem criou o anúncio (listing.createdBy).
O vínculo com a negociação não vem aqui. GET /api/v1/crm/deals/{id} devolve assignedToId (UUID do usuário), sem nome, foto ou telefone. Para montar o mesmo cartão depois que o lead existe, resolva esse id em:
GET /api/sites/{siteId}/brokers/public?ids={userId}ids é obrigatório (um ou mais, separados por vírgula, até 100). A resposta é { "brokers": [...] } com id, name, image, phone, creci e também email. O e-mail fica no servidor de vocês; não repasse ao browser. image pode ser um id de imagem, não uma URL pronta — o endpoint do anúncio acima já devolve imageUrl.
| Status | Quando |
|---|---|
| 204 | Anúncio sem corretor |
| 404 | Site inexistente, inativo, ou siteId que o diretório não resolve |
| 400 | tenantId informado e inválido |
5. Imóveis Similares
Seção intitulada “5. Imóveis Similares”Retorna imóveis similares ao anúncio informado (mesmo formato de card de GET /listings). Usado para carrosséis na página do imóvel.
GET /api/sites/{siteId}/listings/{listingId}/similar?limit=6Parâmetros
Seção intitulada “Parâmetros”| Parâmetro | Tipo | Descrição |
|---|---|---|
siteId | string | UUID do site |
listingId | string | UUID do anúncio ou código (listingCode) |
limit | number | Quantidade máxima (default: 6, máximo: 12) |
Resposta de Sucesso (200)
Seção intitulada “Resposta de Sucesso (200)”{ "listings": [ { "id": "listing-uuid", "listingCode": "APT-002", "path": "/imoveis/sp/sao-paulo/copacabana/id-APT-002", "title": "Apartamento 2 quartos próximo", "description": "...", "businessType": "sale", "price": 820000, "priceVisibility": "public", "status": "active", "featured": false, "property": { "type": "apartment", "bedrooms": 2, "bathrooms": 1, "suites": 0, "parkingSpaces": 1, "totalArea": 72, "usableArea": 68 }, "location": { "street": "Rua Example", "number": "", "neighborhood": "Copacabana", "city": "Rio de Janeiro", "state": "RJ", "postalCode": "" }, "coverImage": { "src": "https://cdn.../image.jpg", "alt": "Apartamento 2 quartos próximo" }, "createdAt": "2026-01-01T00:00:00.000Z", "updatedAt": "2026-01-02T00:00:00.000Z" } ], "total": 1}Se o imóvel de referência não existir ou não for visível no site, a API responde 200 com
listings: [](o carrossel fica vazio). Erros 404/403 ocorrem apenas quando o site não existe ou está inativo.
Exemplo cURL
Seção intitulada “Exemplo cURL”curl "https://app.garagem.ai/api/sites/{siteId}/listings/{listingId}/similar?limit=6"Cards do Site SDK (
GET /listings, similares): o formato amigável (property.bedrooms,coverImage, etc.) não incluiisPrimaryMarket,typologyIdnem campos de tipologia. Para lançamentos, usePOST /search,GET /property/{id}e os endpoints de tipologia abaixo.
Mercado primário (lançamentos)
Seção intitulada “Mercado primário (lançamentos)”Lançamentos usam o modelo empreendimento → tipologia → unidade → anúncio. Características (quartos, área, preço de tabela) vivem na tipologia; o anúncio referencia typologyId e herda os valores na busca.
flowchart TD building["GET /buildings/id"] typologies["GET /typologies?buildingId"] typology["GET /typologies/id"] units["GET /typologies/id/units"] typListings["GET /typologies/id/listings"] search["POST /search ou GET /property/id"] search -->|"buildingId"| building search -->|"typologyId"| typology building --> typologies typologies --> typology typology --> units typology --> typListings
Não há API REST pública para criar ou editar tipologias/unidades — isso é feito no CRM ou via integrações (Órulo, DWV, etc.). Parceiros leem via Sites API.
6. Tipologias por empreendimento
Seção intitulada “6. Tipologias por empreendimento”Lista tipologias públicas de um empreendimento (active + showOnSite).
GET /api/sites/{siteId}/typologies?buildingId={buildingId}Parâmetros
Seção intitulada “Parâmetros”| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
buildingId | string | sim | UUID do empreendimento |
Resposta de Sucesso (200)
Seção intitulada “Resposta de Sucesso (200)”{ "typologies": [ { "id": "typology-uuid", "buildingId": "building-uuid", "name": "Tipo A - 2 Dormitórios", "code": "TIP-A", "reference": "509", "description": "Apartamento com varanda gourmet", "bedrooms": 2, "bathrooms": 2, "suites": 1, "totalArea": "68.50", "privateArea": "62.00", "parkingSpaces": 1, "basePrice": "650000.00", "pricePerSqm": "9500.00", "features": {}, "files": [], "availableUnits": 12, "totalUnits": 40, "expectedDelivery": "Dezembro 2026", "constructionStatus": "structure", "order": 0 } ]}| Status | Erro | Descrição |
|---|---|---|
| 400 | buildingId parameter is required | Query buildingId ausente |
| 404 | Site not found | Site inativo ou inexistente |
Exemplo cURL
Seção intitulada “Exemplo cURL”curl "https://app.garagem.ai/api/sites/{siteId}/typologies?buildingId={buildingId}"7. Detalhe da tipologia
Seção intitulada “7. Detalhe da tipologia”GET /api/sites/{siteId}/typologies/{typologyId}GET /api/sites/{siteId}/typologies/{typologyId}?includeUnits=trueQuery Parameters
Seção intitulada “Query Parameters”| Parâmetro | Tipo | Descrição |
|---|---|---|
includeUnits | boolean | Quando true, inclui units e unitStats na resposta |
Resposta com includeUnits=true (200)
Seção intitulada “Resposta com includeUnits=true (200)”{ "typology": { "id": "typology-uuid", "name": "Tipo A - 2 Dormitórios", "buildingId": "building-uuid", "bedrooms": 2, "availableUnits": 12, "totalUnits": 40, "units": [ { "id": "unit-uuid", "typologyId": "typology-uuid", "unitNumber": "1201", "blockTower": "Torre A", "floor": "12", "status": "available", "price": null } ], "unitStats": { "available": 12, "reserved": 3, "sold": 20, "unavailable": 5, "total": 40 } }}Apenas unidades com
showOnSite: truesão retornadas. Campos de comprador/reserva interna não são expostos na documentação de integração.
| Status | Erro | Descrição |
|---|---|---|
| 404 | Typology not found | Tipologia inexistente, inativa ou oculta do site |
| 404 | Site not found | Site inativo ou inexistente |
8. Unidades da tipologia
Seção intitulada “8. Unidades da tipologia”GET /api/sites/{siteId}/typologies/{typologyId}/unitsRetorna unidades com showOnSite: true, ordenadas por unitNumber.
Campos da unidade (resposta pública)
Seção intitulada “Campos da unidade (resposta pública)”| Campo | Tipo | Descrição |
|---|---|---|
id | uuid | Identificador |
typologyId | uuid | Tipologia |
unitNumber | string | Número da unidade (ex.: "1201", "A") |
blockTower | string | Bloco ou torre |
floor | string | Andar |
status | enum | available, reserved, sold, unavailable |
price | decimal | Preço override (null = usar preço da tipologia/anúncio) |
propertyId | uuid | Imóvel gerado após venda (geralmente null no site) |
Resposta (200)
Seção intitulada “Resposta (200)”{ "units": [ { "id": "unit-uuid", "typologyId": "typology-uuid", "unitNumber": "1201", "blockTower": "Torre A", "floor": "12", "status": "available", "price": null } ]}9. Anúncios da tipologia
Seção intitulada “9. Anúncios da tipologia”Anúncios públicos e ativos vinculados à tipologia (destino de CTA em páginas de lançamento).
GET /api/sites/{siteId}/typologies/{typologyId}/listingsResposta (200)
Seção intitulada “Resposta (200)”{ "listings": [ { "id": "listing-uuid", "title": "Tipo A — 2 dormitórios", "description": "...", "type": "sale", "price": "650000.00", "listingCode": "LAN-001", "typologyId": "typology-uuid", "isPrimaryMarket": "true", "visibility": "public", "stage": "active" } ]}
isPrimaryMarketvem como string"true"/"false"neste endpoint (formato da coluna no banco).
10. Empreendimento (building)
Seção intitulada “10. Empreendimento (building)”GET /api/sites/{siteId}/buildings/{buildingId}Retorna o registro completo do empreendimento/condomínio. Campos relevantes para lançamentos:
| Campo | Tipo | Descrição |
|---|---|---|
id | uuid | Identificador |
name | string | Nome do empreendimento |
description | string | Descrição |
developer | string | Construtora |
publisher | string | Incorporadora / anunciante (quando distinto) |
administrator | string | Administradora |
launchDate | date | Data de lançamento comercial |
openingDate | date | Previsão de entrega |
numberOfTowers | number | Número de torres |
buildingPermit | string | Alvará / matrícula (texto livre) |
portfolio | string[] | Tags (ex.: ["Lançamento"]) |
totalUnits | number | Total de unidades |
floors | number | Andares |
yearBuilt | number | Ano de construção |
amenities | object | Comodidades do empreendimento |
locationId | uuid | Localização vinculada |
Resposta (200)
Seção intitulada “Resposta (200)”{ "building": { "id": "building-uuid", "name": "Residencial Exemplo", "developer": "Construtora Exemplo", "launchDate": "2025-03-01", "openingDate": "2027-06-01", "numberOfTowers": 2, "totalUnits": 120, "amenities": { "pool": true, "gym": true } }}| Status | Erro | Descrição |
|---|---|---|
| 404 | Building not found | Empreendimento inexistente |
| 404 | Site not found | Site inativo ou inexistente |
Exemplo cURL
Seção intitulada “Exemplo cURL”curl "https://app.garagem.ai/api/sites/{siteId}/buildings/{buildingId}"Fluxo de integração (lançamento)
Seção intitulada “Fluxo de integração (lançamento)”// 1. Buscar lançamentos na regiãoconst search = await fetch( `https://app.garagem.ai/api/sites/${siteId}/search`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ q: "*", filter_by: "marketType:=primary && location_city:=`São Paulo`", per_page: 12, }), }).then((r) => r.json());
const hit = search.hits[0]?.document;if (!hit?.buildingId) return;
// 2. Carregar tipologias do empreendimentoconst { typologies } = await fetch( `https://app.garagem.ai/api/sites/${siteId}/typologies?buildingId=${hit.buildingId}`).then((r) => r.json());
// 3. Detalhe com unidades (opcional)const { typology } = await fetch( `https://app.garagem.ai/api/sites/${siteId}/typologies/${typologies[0].id}?includeUnits=true`).then((r) => r.json());11. CMS (conteúdo publicado)
Seção intitulada “11. CMS (conteúdo publicado)”Leitura pública de coleções e entradas CMS publicadas. Drafts e escritas continuam exclusivos do editor / worker CMS.
GET /api/sites/{siteId}/cms/collectionsGET /api/sites/{siteId}/cms/collections/{slug}GET /api/sites/{siteId}/cms/{collection}/entries?limit=50&offset=0GET /api/sites/{siteId}/cms/{collection}/entries/{entrySlug}Listar coleções
Seção intitulada “Listar coleções”curl "https://app.garagem.ai/api/sites/{siteId}/cms/collections"{ "collections": [ { "id": "col_blog", "name": "Blog Posts", "slug": "blog", "templatePageId": "blog-post", "fields": [ { "id": "title", "label": "Título", "type": "text", "required": true }, { "id": "body", "label": "Conteúdo", "type": "richText", "required": true } ], "createdAt": "2026-01-01T00:00:00.000Z", "updatedAt": "2026-01-01T00:00:00.000Z" } ]}Listar entradas publicadas
Seção intitulada “Listar entradas publicadas”A API sempre força status=published no upstream. Query params aceitos: limit (default 50, máx. 100) e offset.
curl "https://app.garagem.ai/api/sites/{siteId}/cms/blog/entries?limit=10"{ "entries": [ { "id": "ent_abc", "collectionId": "col_blog", "slug": "meu-primeiro-post", "status": "published", "fields": { "title": "Meu primeiro post", "body": "<p>...</p>", "excerpt": "Resumo" }, "seo": { "title": "Meu primeiro post | Blog", "description": "Resumo" }, "publishedAt": "2026-02-01T12:00:00.000Z", "createdAt": "2026-01-20T00:00:00.000Z", "updatedAt": "2026-02-01T12:00:00.000Z" } ], "total": 1, "limit": 10, "offset": 0}Obter uma entrada
Seção intitulada “Obter uma entrada”curl "https://app.garagem.ai/api/sites/{siteId}/cms/blog/entries/meu-primeiro-post"{ "entry": { "id": "ent_abc", "slug": "meu-primeiro-post", "status": "published", "fields": { "title": "Meu primeiro post", "body": "<p>...</p>" }, "seo": null, "publishedAt": "2026-02-01T12:00:00.000Z", "createdAt": "2026-01-20T00:00:00.000Z", "updatedAt": "2026-02-01T12:00:00.000Z" }}| Status | Erro | Descrição |
|---|---|---|
| 404 | Site not found | Site inexistente ou inativo |
| 404 | Collection not found | Coleção inexistente |
| 404 | Entry not found | Entrada inexistente ou não publicada |
| 502 | CMS upstream unavailable | Worker CMS indisponível |
12. Localizações Disponíveis
Seção intitulada “12. Localizações Disponíveis”Retorna todas as localizações com imóveis disponíveis no site.
GET /api/sites/{siteId}/locationsGET /api/sites/{siteId}/locations?segment={segment}Parâmetros
Seção intitulada “Parâmetros”| Parâmetro | Tipo | Descrição |
|---|---|---|
segment | string | Segmento de URL para match (ex: bela-vista) |
Resposta sem segment (200)
Seção intitulada “Resposta sem segment (200)”{ "states": { "sp": { "slug": "sp", "name": "SP", "count": 150 }, "rj": { "slug": "rj", "name": "RJ", "count": 80 } }, "cities": { "sao-paulo": { "slug": "sao-paulo", "name": "São Paulo", "count": 120 }, "rio-de-janeiro": { "slug": "rio-de-janeiro", "name": "Rio de Janeiro", "count": 80 } }, "neighborhoods": { "bela-vista": { "slug": "bela-vista", "name": "Bela Vista", "count": 25 }, "copacabana": { "slug": "copacabana", "name": "Copacabana", "count": 40 } }, "streets": { "av-paulista": { "slug": "av-paulista", "name": "Av. Paulista", "count": 5 } }}Resposta com segment (200)
Seção intitulada “Resposta com segment (200)”{ "segment": "bela-vista", "match": { "type": "neighborhood", "value": "Bela Vista" }}Este endpoint é útil para:
- Construir menus de navegação por localização
- Resolver URLs amigáveis (
/imoveis/sao-paulo/bela-vista) - Autocomplete em campos de busca
13. Validar URL de Localização
Seção intitulada “13. Validar URL de Localização”Valida um caminho hierárquico de localização e retorna breadcrumbs para navegação.
GET /api/sites/{siteId}/locations/validate?path={path}Parâmetros
Seção intitulada “Parâmetros”| Parâmetro | Tipo | Descrição |
|---|---|---|
path | string | Caminho de localização (ex: sp/sao-paulo/lapa) |
baseRoute | string | Rota base para breadcrumbs (default: imoveis) |
Resposta de Sucesso (200)
Seção intitulada “Resposta de Sucesso (200)”{ "valid": true, "matches": { "state": "SP", "city": "São Paulo", "neighborhood": "Lapa" }, "breadcrumb": [ { "slug": "sp", "name": "SP", "type": "state", "path": "/imoveis/sp" }, { "slug": "sao-paulo", "name": "São Paulo", "type": "city", "path": "/imoveis/sp/sao-paulo" }, { "slug": "lapa", "name": "Lapa", "type": "neighborhood", "path": "/imoveis/sp/sao-paulo/lapa" } ]}Resposta de Erro (Localização Inválida)
Seção intitulada “Resposta de Erro (Localização Inválida)”{ "valid": false, "error": "neighborhood_not_in_city", "message": "Bairro 'xyz' não encontrado em São Paulo"}Códigos de Erro
Seção intitulada “Códigos de Erro”| Código | Descrição |
|---|---|
missing_path | Parâmetro path não fornecido |
empty_path | Caminho vazio |
path_too_deep | Mais de 4 níveis (estado/cidade/bairro/rua) |
state_not_found | Estado não encontrado |
city_not_in_state | Cidade não pertence ao estado |
neighborhood_not_in_city | Bairro não pertence à cidade |
no_listings | Nenhum imóvel nessa localização |
Exemplo de Uso
Seção intitulada “Exemplo de Uso”curl "https://app.garagem.ai/api/sites/{siteId}/locations/validate?path=sp/sao-paulo/bela-vista&baseRoute=imoveis"🔗 Construindo URLs SEO-Friendly
Seção intitulada “🔗 Construindo URLs SEO-Friendly”A Sites API fornece ferramentas para criar URLs amigáveis para SEO como /imoveis/sp/sao-paulo/bela-vista.
Estrutura de URL Recomendada
Seção intitulada “Estrutura de URL Recomendada”/{baseRoute}/{estado}/{cidade}/{bairro}/{rua}/imoveis/sp/sao-paulo/bela-vista/imoveis/rj/rio-de-janeiro/copacabanaFluxo Completo para WordPress/Sites Externos
Seção intitulada “Fluxo Completo para WordPress/Sites Externos”1. Carregar Localizações para Menu
Seção intitulada “1. Carregar Localizações para Menu”// Obter todas as localizações disponíveisconst response = await fetch( `https://app.garagem.ai/api/sites/${siteId}/locations`);const locations = await response.json();
// Construir menu hierárquicoObject.entries(locations.states).forEach(([slug, data]) => { console.log(`Estado: ${data.name} - /imoveis/${slug}`);});
Object.entries(locations.cities).forEach(([slug, data]) => { console.log(`Cidade: ${data.name} - /imoveis/{estado}/${slug}`);});2. Resolver URL ao Carregar Página
Seção intitulada “2. Resolver URL ao Carregar Página”Quando o usuário acessa /imoveis/sp/sao-paulo/bela-vista:
// Validar o caminho e obter breadcrumbsconst path = "sp/sao-paulo/bela-vista";const response = await fetch( `https://app.garagem.ai/api/sites/${siteId}/locations/validate?path=${path}`);const validation = await response.json();
if (!validation.valid) { // Redirecionar para 404 ou página genérica return redirect("/imoveis");}
// Usar matches para filtrar buscaconst filters = [];if (validation.matches.state) { filters.push(`location_state:=${validation.matches.state}`);}if (validation.matches.city) { filters.push(`location_city:=\`${validation.matches.city}\``);}if (validation.matches.neighborhood) { filters.push(`location_neighborhood:=\`${validation.matches.neighborhood}\``);}
// Buscar imóveis com filtrosconst searchResponse = await fetch( `https://app.garagem.ai/api/sites/${siteId}/search`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ q: "*", query_by: "title", filter_by: filters.join(" && "), per_page: 12, }), });
// Renderizar breadcrumbsvalidation.breadcrumb.forEach((item) => { console.log(`<a href="${item.path}">${item.name}</a>`);});3. Gerar Sitemap de Localizações
Seção intitulada “3. Gerar Sitemap de Localizações”// Buscar todas as localizaçõesconst locations = await fetch( `https://app.garagem.ai/api/sites/${siteId}/locations`).then((r) => r.json());
// Gerar URLs para sitemapconst sitemapUrls = [];
// EstadosObject.entries(locations.states).forEach(([slug, data]) => { sitemapUrls.push({ loc: `https://seusite.com/imoveis/${slug}`, changefreq: "weekly", priority: 0.8, });});
// CidadesObject.entries(locations.cities).forEach(([slug, data]) => { // Você precisaria mapear cidade -> estado para URL completa sitemapUrls.push({ loc: `https://seusite.com/imoveis/{estado}/${slug}`, changefreq: "weekly", priority: 0.7, });});
// BairrosObject.entries(locations.neighborhoods).forEach(([slug, data]) => { sitemapUrls.push({ loc: `https://seusite.com/imoveis/{estado}/{cidade}/${slug}`, changefreq: "daily", priority: 0.9, });});Exemplo PHP (WordPress)
Seção intitulada “Exemplo PHP (WordPress)”/** * Resolver URL de localização no WordPress */function garagem_resolve_location_url($path) { $site_id = get_option('garagem_site_id');
$response = wp_remote_get( "https://app.garagem.ai/api/sites/{$site_id}/locations/validate?" . http_build_query(['path' => $path, 'baseRoute' => 'imoveis']) );
if (is_wp_error($response)) { return false; }
$data = json_decode(wp_remote_retrieve_body($response), true);
if (!$data['valid']) { return false; }
return $data;}
/** * Gerar breadcrumbs HTML */function garagem_render_breadcrumbs($breadcrumb) { echo '<nav class="breadcrumb">'; echo '<a href="/imoveis">Imóveis</a>';
foreach ($breadcrumb as $item) { echo ' > <a href="' . esc_url($item['path']) . '">' . esc_html($item['name']) . '</a>'; }
echo '</nav>';}14. Links de Compartilhamento
Seção intitulada “14. Links de Compartilhamento”Resolve tokens de compartilhamento de imóveis ou seleções.
GET /api/sites/share/{token}POST /api/sites/share/{token}Body (POST - para links protegidos)
Seção intitulada “Body (POST - para links protegidos)”{ "password": "senha123"}Resposta de Sucesso (200)
Seção intitulada “Resposta de Sucesso (200)”{ "token": { "id": "token-uuid", "entityType": "listing", "entityId": "listing-uuid", "dealId": "deal-uuid", "createdBy": "user-uuid" }, "entity": { "type": "listing", "listing": { ... }, "property": { ... }, "medias": [ { "id": "media-uuid", "src": "https://cdn.../image.jpg", "alt": "Sala de estar", "type": "image", "category": "Interior", "order": 0, "isPrimary": true } ] }, "branding": { "name": "Imobiliária Exemplo", "logo": "https://...", "primaryColor": "#FF5500" }, "tenantConfig": { "propertyFeatures": [...], "buildingFeatures": [...], "primaryMarketEnabled": false, "propertyTypes": [...] }, "site": { "hasSite": true, "domain": "imoveis.exemplo.com", "siteId": "site-uuid" }, "tenantId": "tenant-uuid"}Tipos de Entidade
Seção intitulada “Tipos de Entidade”| Tipo | Descrição |
|---|---|
listing | Imóvel individual |
shortlist | Seleção de imóveis para cliente |
seller_report | Relatório do proprietário |
visit_itinerary | Roteiro de visitas |
| Status | Erro | Descrição |
|---|---|---|
| 404 | Share link not found | Token não encontrado ou inativo |
| 410 | Share link has expired | Token expirado |
| 401 | Password required | Link protegido por senha |
| 403 | Invalid password | Senha incorreta |
🔧 Headers de Cache
Seção intitulada “🔧 Headers de Cache”Todos os endpoints retornam headers de cache otimizados:
Cache-Control: public, max-age=60, stale-while-revalidate=300Access-Control-Allow-Origin: *Access-Control-Allow-Methods: GET, POST, OPTIONSAccess-Control-Allow-Headers: Content-Type, AuthorizationRecomendação: Use uma CDN (Cloudflare, Vercel Edge) para máxima performance.
🔒 Dados Sensíveis
Seção intitulada “🔒 Dados Sensíveis”Campos Removidos
Seção intitulada “Campos Removidos”Para proteção de dados, os seguintes campos são automaticamente removidos das respostas:
owner_phone,owner_email,owner_whatsappcontact_phone,contact_emailowner_name,owner_cpf,owner_documentrealtor_phone,realtor_emailinternal_notes,private_noteslocation_number(número do endereço)contacts(array completo de contatos)
Coordenadas Mascaradas
Seção intitulada “Coordenadas Mascaradas”Latitude e longitude são arredondadas para 3 casas decimais (~100m de precisão), protegendo a localização exata do imóvel enquanto permite mostrar no mapa.
📱 Exemplos de Integração
Seção intitulada “📱 Exemplos de Integração”JavaScript/Fetch
Seção intitulada “JavaScript/Fetch”// Buscar imóveisconst response = await fetch( `https://app.garagem.ai/api/sites/${siteId}/search`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ q: "*", query_by: "title,description", filter_by: "bedrooms:>=2", per_page: 12, }), });const data = await response.json();console.log(`Encontrados ${data.found} imóveis`);PHP/WordPress
Seção intitulada “PHP/WordPress”// Buscar configuração do site$response = wp_remote_get( "https://cdn.fndn.ai/site/api/sites/resolve?siteId={$site_id}");$config = json_decode(wp_remote_retrieve_body($response), true);
// Buscar imóveis$search_response = wp_remote_post( "https://app.garagem.ai/api/sites/{$site_id}/search", array( 'headers' => array('Content-Type' => 'application/json'), 'body' => json_encode(array( 'q' => '*', 'query_by' => 'title', 'per_page' => 12 )) ));$listings = json_decode(wp_remote_retrieve_body($search_response), true);import requests
# Buscar imóveisresponse = requests.post( f"https://app.garagem.ai/api/sites/{site_id}/search", json={ "q": "*", "query_by": "title,description", "filter_by": "bedrooms:>=2", "per_page": 12 })data = response.json()print(f"Encontrados {data['found']} imóveis")🚀 Rate Limits
Seção intitulada “🚀 Rate Limits”| Endpoint | Limite |
|---|---|
/resolve | 100 req/min |
/public-config | 100 req/min |
/search | 60 req/min |
/property/{id} | 100 req/min |
/listings/{id}/similar | 60 req/min |
/typologies | 60 req/min |
/typologies/{id} | 60 req/min |
/typologies/{id}/units | 60 req/min |
/typologies/{id}/listings | 60 req/min |
/buildings/{id} | 60 req/min |
/cms/... | 60 req/min |
/locations | 30 req/min |
/share/{token} | 60 req/min |
Dica: Use cache no cliente para reduzir chamadas. Os headers Cache-Control indicam por quanto tempo os dados podem ser cacheados.
📚 Próximos Passos
Seção intitulada “📚 Próximos Passos”- WordPress Plugin: Instale pelo diretório WordPress.org (Garagem) e veja 11 - Plugin WordPress para uso e shortcodes
- Webhooks: Configure webhooks para receber atualizações em tempo real
- SDK JavaScript: Em desenvolvimento para facilitar integração frontend