Pular para o conteúdo

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-sdk oferece 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.


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)
https://app.garagem.ai/api/sites

Para usar a Sites API, você precisa do Site ID do seu site configurado no Garagem. Existem duas formas de obtê-lo:

  1. Acesse Configurações → Canais → Sites no Garagem
  2. Clique no site desejado
  3. O Site ID aparece na URL: https://app.garagem.ai/configuracoes/sites/{SITE_ID}
  4. Ou copie do campo “ID do Site” na página de configuração

Se você já tem um domínio configurado, pode usar o endpoint /resolve para obter o Site ID:

Janela do terminal
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.


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âmetroTipoObrigatórioDescrição
domainstring*Domínio do site (ex: imoveis.exemplo.com)
siteIdstring*UUID do site

* Pelo menos um dos parâmetros é obrigatório.

{
"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
}

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.

Indica se o tenant tem mercado primário (lançamentos) habilitado no CRM.

ValorComportamento
trueSites Garagem exibem fluxos de lançamento (tipologias, unidades, empreendimento)
falseUI 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.

StatusErroDescrição
400Invalid parameterParâmetro domain ou siteId ausente
404Site not foundSite não encontrado ou inativo
404Site not publishedSite não publicado
Janela do terminal
curl "https://cdn.fndn.ai/site/api/sites/resolve?domain=imoveis.exemplo.com"

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.

UsoCódigoOnde aparece
Português (fonte)pt-BRtitle, description
Inglêsentitle_en, description_en
Espanholestitle_es, description_es

Use en e es — não en-US / es-ES. O locale pt-BR nunca aparece dentro de um objeto translations na API REST; fica só nas colunas principais.

Nos hits de POST /api/sites/{siteId}/search, no GET /api/sites/{siteId}/property/{id} e no feed por canal, cada documento pode incluir:

CampoDescrição
title / descriptionTexto em português
title_en / description_enTradução em inglês (string vazia se ausente)
title_es / description_esTraduçã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).

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 || "",
};
}

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.

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.

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}.


Obtém a configuração pública de um site específico pelo ID.

GET /api/sites/{siteId}/public-config

Nota: O endpoint /resolve é preferível pois combina lookup + config em uma única chamada.

Mesma estrutura do endpoint /resolve.


Proxy para busca no Typesense com filtros do site automaticamente aplicados.

POST /api/sites/{siteId}/search
Content-Type: application/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âmetroTipoDescrição
qstringTermo de busca (use * para todos)
query_bystringCampos 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_bystringFiltros Typesense
sort_bystringOrdenação (ex: price:asc, createdAt:desc)
per_pagenumberResultados por página (max: 250)
pagenumberPágina atual
facet_bystringCampos para facets/agregações

O endpoint automaticamente aplica:

  1. visibility:=public - Apenas anúncios públicos
  2. stage:=active - Apenas anúncios ativos (ou estágio configurado pelo tenant)
  3. Listing Criteria do Site - Filtros configurados no painel do site (inclui marketType:=primary ou marketType:=secondary quando configurado)

Combine com filter_by na busca Typesense:

FiltroExemploDescrição
marketTypemarketType:=primarySó lançamentos (secondary = revenda)
constructionStatusconstructionStatus:=plantStatus da obra (valor livre ou código do CRM)
hasAvailableUnitshasAvailableUnits:=trueTipologia 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, totalArea e privateArea no documento de busca vêm da tipologia vinculada (typologyId), não do imóvel base.

{
"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
}

Presentes em cada document quando o anúncio é de lançamento (ou null / ausentes em revenda):

CampoTipoDescrição
isPrimaryMarketbooleantrue se lançamento
marketTypestring"primary" ou "secondary"
typologyIduuid | nullTipologia vinculada (pode ser null em lançamentos só com flag)
buildingIduuid | nullEmpreendimento / condomínio
typologyNamestring | nullNome da tipologia
typologyCodestring | nullCódigo interno da tipologia
constructionStatusstring | nullStatus da obra
expectedDeliverystring | nullPrevisão de entrega (ex.: "Dezembro 2026")
availableUnitsnumber | nullUnidades disponíveis na tipologia
typologyTotalUnitsnumber | nullTotal de unidades da tipologia
typologyBasePricenumber | nullPreço base da tipologia
typologyPricePerSqmnumber | nullPreço por m² da tipologia
hasAvailableUnitsboolean | nulltrue 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"
}
}

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).

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.

Janela do terminal
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
}'

Obtém dados completos de um imóvel específico.

GET /api/sites/{siteId}/property/{id}
ParâmetroTipoDescrição
siteIdstringUUID do site
idstringUUID do imóvel ou código do anúncio
ParâmetroTipoDescrição
shortlistIdstringID da seleção (permite ver imóveis não publicados)
{
"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 typologyId e buildingId para carregar tipologias e empreendimento nos endpoints 6–10.

imageOrigins (opcional) — alinhado com imageUrls. Proveniência de staging publicado: cada slot é "original", exceto quando existe variante publicada de staging para aquele image_ids[i] (fal, arquitetodebolso, …). Independente de a URL servida ter sido trocada. Só aparece com fotos; hasProviderStagedPhotos é true se algum slot ≠ "original".

StatusErroDescrição
404Site not foundSite não encontrado
403Site not activeSite inativo
404Property not foundImóvel não encontrado/visível

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}/broker

O 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.

{
"id": "user-uuid",
"name": "Ana Souza",
"imageUrl": "https://cdn.fndn.ai/...",
"phone": "+5511999999999",
"creci": "12345-F"
}
CampoConteúdo
idID do usuário
nameNome do usuário. Não há campo separado de nome público.
imageUrlURL da foto, ou null
phoneTelefone 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.
creciCRECI, 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.

StatusQuando
204Anúncio sem corretor
404Site inexistente, inativo, ou siteId que o diretório não resolve
400tenantId informado e inválido

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=6
ParâmetroTipoDescrição
siteIdstringUUID do site
listingIdstringUUID do anúncio ou código (listingCode)
limitnumberQuantidade máxima (default: 6, máximo: 12)
{
"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.

Janela do terminal
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 inclui isPrimaryMarket, typologyId nem campos de tipologia. Para lançamentos, use POST /search, GET /property/{id} e os endpoints de tipologia abaixo.


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.

Lista tipologias públicas de um empreendimento (active + showOnSite).

GET /api/sites/{siteId}/typologies?buildingId={buildingId}
ParâmetroTipoObrigatórioDescrição
buildingIdstringsimUUID do empreendimento
{
"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
}
]
}
StatusErroDescrição
400buildingId parameter is requiredQuery buildingId ausente
404Site not foundSite inativo ou inexistente
Janela do terminal
curl "https://app.garagem.ai/api/sites/{siteId}/typologies?buildingId={buildingId}"

GET /api/sites/{siteId}/typologies/{typologyId}
GET /api/sites/{siteId}/typologies/{typologyId}?includeUnits=true
ParâmetroTipoDescrição
includeUnitsbooleanQuando true, inclui units e unitStats na resposta
{
"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: true são retornadas. Campos de comprador/reserva interna não são expostos na documentação de integração.

StatusErroDescrição
404Typology not foundTipologia inexistente, inativa ou oculta do site
404Site not foundSite inativo ou inexistente

GET /api/sites/{siteId}/typologies/{typologyId}/units

Retorna unidades com showOnSite: true, ordenadas por unitNumber.

CampoTipoDescrição
iduuidIdentificador
typologyIduuidTipologia
unitNumberstringNúmero da unidade (ex.: "1201", "A")
blockTowerstringBloco ou torre
floorstringAndar
statusenumavailable, reserved, sold, unavailable
pricedecimalPreço override (null = usar preço da tipologia/anúncio)
propertyIduuidImóvel gerado após venda (geralmente null no site)
{
"units": [
{
"id": "unit-uuid",
"typologyId": "typology-uuid",
"unitNumber": "1201",
"blockTower": "Torre A",
"floor": "12",
"status": "available",
"price": null
}
]
}

Anúncios públicos e ativos vinculados à tipologia (destino de CTA em páginas de lançamento).

GET /api/sites/{siteId}/typologies/{typologyId}/listings
{
"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"
}
]
}

isPrimaryMarket vem como string "true" / "false" neste endpoint (formato da coluna no banco).


GET /api/sites/{siteId}/buildings/{buildingId}

Retorna o registro completo do empreendimento/condomínio. Campos relevantes para lançamentos:

CampoTipoDescrição
iduuidIdentificador
namestringNome do empreendimento
descriptionstringDescrição
developerstringConstrutora
publisherstringIncorporadora / anunciante (quando distinto)
administratorstringAdministradora
launchDatedateData de lançamento comercial
openingDatedatePrevisão de entrega
numberOfTowersnumberNúmero de torres
buildingPermitstringAlvará / matrícula (texto livre)
portfoliostring[]Tags (ex.: ["Lançamento"])
totalUnitsnumberTotal de unidades
floorsnumberAndares
yearBuiltnumberAno de construção
amenitiesobjectComodidades do empreendimento
locationIduuidLocalização vinculada
{
"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 }
}
}
StatusErroDescrição
404Building not foundEmpreendimento inexistente
404Site not foundSite inativo ou inexistente
Janela do terminal
curl "https://app.garagem.ai/api/sites/{siteId}/buildings/{buildingId}"
// 1. Buscar lançamentos na região
const 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 empreendimento
const { 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());

Leitura pública de coleções e entradas CMS publicadas. Drafts e escritas continuam exclusivos do editor / worker CMS.

GET /api/sites/{siteId}/cms/collections
GET /api/sites/{siteId}/cms/collections/{slug}
GET /api/sites/{siteId}/cms/{collection}/entries?limit=50&offset=0
GET /api/sites/{siteId}/cms/{collection}/entries/{entrySlug}
Janela do terminal
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"
}
]
}

A API sempre força status=published no upstream. Query params aceitos: limit (default 50, máx. 100) e offset.

Janela do terminal
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
}
Janela do terminal
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"
}
}
StatusErroDescrição
404Site not foundSite inexistente ou inativo
404Collection not foundColeção inexistente
404Entry not foundEntrada inexistente ou não publicada
502CMS upstream unavailableWorker CMS indisponível

Retorna todas as localizações com imóveis disponíveis no site.

GET /api/sites/{siteId}/locations
GET /api/sites/{siteId}/locations?segment={segment}
ParâmetroTipoDescrição
segmentstringSegmento de URL para match (ex: bela-vista)
{
"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 }
}
}
{
"segment": "bela-vista",
"match": {
"type": "neighborhood",
"value": "Bela Vista"
}
}

Este endpoint é útil para:

  1. Construir menus de navegação por localização
  2. Resolver URLs amigáveis (/imoveis/sao-paulo/bela-vista)
  3. Autocomplete em campos de busca

Valida um caminho hierárquico de localização e retorna breadcrumbs para navegação.

GET /api/sites/{siteId}/locations/validate?path={path}
ParâmetroTipoDescrição
pathstringCaminho de localização (ex: sp/sao-paulo/lapa)
baseRoutestringRota base para breadcrumbs (default: imoveis)
{
"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"
}
]
}
{
"valid": false,
"error": "neighborhood_not_in_city",
"message": "Bairro 'xyz' não encontrado em São Paulo"
}
CódigoDescrição
missing_pathParâmetro path não fornecido
empty_pathCaminho vazio
path_too_deepMais de 4 níveis (estado/cidade/bairro/rua)
state_not_foundEstado não encontrado
city_not_in_stateCidade não pertence ao estado
neighborhood_not_in_cityBairro não pertence à cidade
no_listingsNenhum imóvel nessa localização
Janela do terminal
curl "https://app.garagem.ai/api/sites/{siteId}/locations/validate?path=sp/sao-paulo/bela-vista&baseRoute=imoveis"

A Sites API fornece ferramentas para criar URLs amigáveis para SEO como /imoveis/sp/sao-paulo/bela-vista.

/{baseRoute}/{estado}/{cidade}/{bairro}/{rua}
/imoveis/sp/sao-paulo/bela-vista
/imoveis/rj/rio-de-janeiro/copacabana
// Obter todas as localizações disponíveis
const response = await fetch(
`https://app.garagem.ai/api/sites/${siteId}/locations`
);
const locations = await response.json();
// Construir menu hierárquico
Object.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}`);
});

Quando o usuário acessa /imoveis/sp/sao-paulo/bela-vista:

// Validar o caminho e obter breadcrumbs
const 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 busca
const 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 filtros
const 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 breadcrumbs
validation.breadcrumb.forEach((item) => {
console.log(`<a href="${item.path}">${item.name}</a>`);
});
// Buscar todas as localizações
const locations = await fetch(
`https://app.garagem.ai/api/sites/${siteId}/locations`
).then((r) => r.json());
// Gerar URLs para sitemap
const sitemapUrls = [];
// Estados
Object.entries(locations.states).forEach(([slug, data]) => {
sitemapUrls.push({
loc: `https://seusite.com/imoveis/${slug}`,
changefreq: "weekly",
priority: 0.8,
});
});
// Cidades
Object.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,
});
});
// Bairros
Object.entries(locations.neighborhoods).forEach(([slug, data]) => {
sitemapUrls.push({
loc: `https://seusite.com/imoveis/{estado}/{cidade}/${slug}`,
changefreq: "daily",
priority: 0.9,
});
});
/**
* 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>';
}

Resolve tokens de compartilhamento de imóveis ou seleções.

GET /api/sites/share/{token}
POST /api/sites/share/{token}
{
"password": "senha123"
}
{
"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"
}
TipoDescrição
listingImóvel individual
shortlistSeleção de imóveis para cliente
seller_reportRelatório do proprietário
visit_itineraryRoteiro de visitas
StatusErroDescrição
404Share link not foundToken não encontrado ou inativo
410Share link has expiredToken expirado
401Password requiredLink protegido por senha
403Invalid passwordSenha incorreta

Todos os endpoints retornam headers de cache otimizados:

Cache-Control: public, max-age=60, stale-while-revalidate=300
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization

Recomendação: Use uma CDN (Cloudflare, Vercel Edge) para máxima performance.


Para proteção de dados, os seguintes campos são automaticamente removidos das respostas:

  • owner_phone, owner_email, owner_whatsapp
  • contact_phone, contact_email
  • owner_name, owner_cpf, owner_document
  • realtor_phone, realtor_email
  • internal_notes, private_notes
  • location_number (número do endereço)
  • contacts (array completo de contatos)

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.


// Buscar imóveis
const 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`);
// 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óveis
response = 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")

EndpointLimite
/resolve100 req/min
/public-config100 req/min
/search60 req/min
/property/{id}100 req/min
/listings/{id}/similar60 req/min
/typologies60 req/min
/typologies/{id}60 req/min
/typologies/{id}/units60 req/min
/typologies/{id}/listings60 req/min
/buildings/{id}60 req/min
/cms/...60 req/min
/locations30 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.