Documentação para desenvolvedores

    API de Imóveis & Integrações

    Conecte seu CRM, site institucional ou sistema próprio ao Alto Vale Shopping. Publique e atualize imóveis por REST, sincronize a galeria de fotos, leia leads, visitas, lista de espera e mensagens, e receba eventos em tempo real por webhook — tudo autenticado por API Key.

    Visão geral

    A API do Alto Vale Shopping é REST/JSON e escopada por conta: uma chave enxerga e escreve apenas os imóveis do anunciante que a gerou. O escopo vem da própria chave — não existe parâmetro de owner na requisição, e um owner_id enviado no corpo é descartado pelo servidor.

    Base URL

    https://altovaleshopping.com/api/v1

    Autenticação

    X-API-Key: av_live_…

    Versão 1. O path atual não tem prefixo de versão. Mudanças incompatíveis serão publicadas em um namespace novo (/v1/) com aviso prévio aos parceiros com chave ativa — a v1 atual continua respondendo.

    1. Pré-requisitos

    • Uma conta de imobiliária ou corretor aprovada na plataforma. Contas de comprador não geram chaves.
    • Um plano com acesso à API liberado. O plano define também o número máximo de chaves ativas simultâneas — quando o limite é 1, gerar uma nova revoga automaticamente a anterior. Veja os planos.
    • Um endpoint HTTPS público, caso vá usar webhooks (item 9). URLs em HTTP, localhost ou faixas de IP privadas são recusadas na entrega.
    Não vê a seção de API no painel? O recurso é liberado por plano e por configuração da plataforma. Fale com o suporte informando o e-mail da conta.

    2. Gerar a API Key

    1

    Abra as configurações do painel

    Entre na sua conta e vá em Painel → Configurações, aba Integrações. A seção API Keys lista as chaves existentes com prefixo, escopos, limite e data do último uso.

    2

    Clique em “Nova chave”

    Dê um nome interno que identifique o consumidor — “CRM Vista”, “Site institucional”, “Script de importação”. Uma chave por sistema: assim você revoga um integrador sem derrubar os outros.

    3

    Escolha os escopos

    properties:read para leitura e properties:write para criar, editar, remover e mexer em fotos. Marque só o que o sistema realmente precisa — um site que apenas exibe imóveis não deve ter escrita.

    4

    Defina o rate limit

    Padrão de 60 requisições por minuto, contado por chave (não por IP). Para cargas maiores, solicite o aumento ao suporte.

    5

    Copie a chave — ela aparece uma única vez

    O formato é av_live_ + 32 caracteres hexadecimais. A plataforma guarda apenas o hash SHA-256: ninguém — nem o suporte — consegue recuperar o valor depois. Se perder, revogue e gere outra.

    Guarde em cofre de segredos (1Password, AWS Secrets Manager, variável de ambiente do servidor). Nunca no front-end, em repositório ou em app mobile — a chave tem poder de escrita sobre o seu inventário.

    Pronto. A chave já vale para a primeira chamada.

    Revogar: na mesma tela, ícone de lixeira ao lado da chave. A revogação é imediata — a chamada seguinte recebe 401.

    3. Primeira chamada

    Liste os imóveis ativos da sua conta. Se voltar 200 com data e total, a autenticação está correta.

    cURL
    curl 'https://altovaleshopping.com/api/v1/properties?page=1&limit=20&status=ativo' \
      -H "X-API-Key: av_live_SUA_CHAVE_AQUI"
    Node.js / TypeScript
    const API = "https://altovaleshopping.com/api/v1";
    const KEY = process.env.AVS_API_KEY!;   // nunca hardcode a chave
    
    const res = await fetch(`${API}/properties?status=ativo&limit=100`, {
      headers: { "X-API-Key": KEY },
    });
    
    if (!res.ok) throw new Error(`API ${res.status}: ${(await res.json()).error}`);
    
    const { data, total, page, limit } = await res.json();
    console.log(`${data.length} de ${total} imóveis`);
    PHP
    $ch = curl_init("https://altovaleshopping.com/api/v1/properties?status=ativo");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: " . getenv("AVS_API_KEY")]);
    $body = json_decode(curl_exec($ch), true);
    A listagem devolve um subconjunto dos campos (identificação, preço, localização, contadores) mais cover_image — a primeira foto da galeria, para você montar uma listagem sem um GET por imóvel. Para o objeto completo com descrição, características e galeria inteira, use GET /properties/:id.

    4. Escopos e limites

    EscopoPermite
    properties:readGET de lista e de imóvel único
    properties:writePOST, PUT, PATCH, DELETE e endpoints de fotos
    properties:allLeitura + escrita (curinga)

    Um método de escrita com chave só de leitura devolve 403 com Insufficient scope.

    Rate limit: teto por minuto por chave, padrão 60. Ao estourar, a resposta é 429 e a requisição não é processada. Em sincronizações em lote, espace as chamadas em ~1s.

    Paginação: ?page= (base 1) e ?limit= (máximo 100). O total de registros vem em total.

    5. Endpoints

    Método & pathO que faz
    GET /propertiesLista os imóveis da conta. Filtros: status, updated_since. Cada item traz cover_image.
    GET /properties/:idObjeto completo + galeria de imagens.
    POST /propertiesCria imóvel. Sem status informado, entra como rascunho.
    PATCH /properties/:idAtualização parcial — envie só os campos que mudaram.
    PUT /properties/:idIdêntico ao PATCH (campos omitidos permanecem).
    DELETE /properties/:idSoft delete: marca status=removido, não apaga o registro.
    POST /properties/:id/imagesAdiciona ou substitui fotos a partir de URLs públicas.
    DELETE /properties/:id/images/:imageIdRemove uma foto da galeria e do storage.
    GET /leadsLeads do CRM. Filtros: origin, column, updated_since.
    GET /leads/:idLead completo + property_ids vinculados.
    GET /leads/:id/interactionsHistórico do lead: notas, ligações, movimentações.
    GET /visitsAgendamentos de visita. Filtros: status, from, to.
    GET /waitlistLista de espera por perfil de imóvel. Filtro: status.
    GET /messagesConversas do chat. Filtros: property_id, unread, since.

    O corpo de escrita passa por uma allowlist: campos fora dela (id, owner_id, view_count, featured, valores calculados) são descartados em silêncio, sem erro. A lista completa de campos graváveis e o schema do recurso estão na referência OpenAPI.

    Criar e ativar um imóvel
    # 1. cria (entra como rascunho)
    RES=$(curl -sS -X POST "https://altovaleshopping.com/api/v1/properties" \
      -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
      -d '{
        "title": "Casa 3 quartos no Centro",
        "property_type": "venda",
        "category": "casa",
        "price": 450000,
        "address_street": "Rua XV de Novembro",
        "address_number": "100",
        "address_neighborhood": "Centro",
        "address_city": "Rio do Sul",
        "address_state": "SC",
        "address_zip": "89160-000",
        "reference_code": "SEU-ID-EXTERNO-123"
      }')
    ID=$(echo "$RES" | jq -r .id)
    
    # 2. publica
    curl -sS -X PATCH "https://altovaleshopping.com/api/v1/properties/$ID" \
      -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
      -d '{"status":"ativo"}'

    6. CRM, visitas, lista de espera e mensagens

    /properties é o que você publica. Estes quatro recursos são o que o marketplace devolve para a sua conta — o outro lado da integração, para o seu CRM não depender de e-mail ou de alguém olhando o painel.

    RecursoConteúdoEscopo
    /leadsLeads do CRM com origem, urgência, coluna do funil, corretor responsável e histórico de interações.leads:read
    /visitsVisitas agendadas: data, horário, status, contato do visitante e corretor atribuído.visits:read
    /waitlistCompradores esperando por um perfil de imóvel (tipo, região, faixa de preço).waitlist:read
    /messagesConversas do chat da sua conta, com o imóvel relacionado e o marcador de lida.messages:read

    Quem enxerga o quê

    O recorte de linhas vem da chave, nunca de um parâmetro. Chave de imobiliária vê os registros dela e dos corretores vinculados; chave de corretor vinculado vê os próprios — e, em leads, só os atribuídos a ele. Mensagens são a exceção deliberada: uma chave só lê as conversas da própria conta, nunca as de um corretor, porque conversa é dele com o comprador.

    Puxar os leads novos desde a última sincronização
    curl 'https://altovaleshopping.com/api/v1/leads?updated_since=2026-08-01T00:00:00Z&limit=100' \
      -H "X-API-Key: $KEY"
    
    # {
    #   "data": [
    #     {
    #       "id": "uuid",
    #       "name": "Maria Souza",
    #       "email": "[email protected]",
    #       "phone": "47999990000",
    #       "origin": "visita",
    #       "urgency": "alta",
    #       "status_column_id": "col_novo",
    #       "source_property_id": "uuid",
    #       "assigned_broker_id": null,
    #       "created_at": "2026-08-02T13:41:07.201Z"
    #     }
    #   ],
    #   "total": 37, "page": 1, "limit": 100
    # }
    Somente leitura nesta versão. Escrever nesses recursos gera efeito visível para o comprador (mensagem enviada, visita remarcada, lead movido no funil de quem atende), então a v1 sai só com leitura — abrir escrita depois é aditivo e não quebra integração nenhuma. Um POST aqui retorna 403 (não existe escopo de escrita para esses recursos no painel); se o escopo for concedido manualmente, a própria API ainda recusa com 405. Precisa escrever? Fale com o suporte.
    LGPD. Esses endpoints devolvem dados pessoais de compradores (nome, telefone, e-mail, conteúdo de conversa). Trate como tal: transporte e armazenamento seguros, uso restrito ao atendimento imobiliário e exclusão quando solicitada.

    7. Fluxo de integração

    O desenho abaixo é o que recomendamos para um CRM que já tem a carteira de imóveis e quer espelhá-la no marketplace.

    1

    Carga inicial

    Para cada imóvel do seu sistema: POST /properties como rascunho → POST /properties/:id/imagesPATCH status=ativo. Publicar só depois das fotos evita anúncio sem imagem no ar.

    2

    Guarde o mapeamento de IDs

    A API não é idempotente: cada POST cria um registro novo. Salve seu_id → id_da_plataforma no seu banco (e mande também o seu identificador em reference_code / source_url). Sem isso, um retry após timeout duplica o anúncio.

    3

    Atualizações incrementais

    Mudou preço, status ou descrição no seu CRM? Um PATCH com apenas os campos alterados. Não reenvie o objeto inteiro — é mais barato e evita sobrescrever ajustes feitos no painel.

    4

    Baixa do imóvel

    Vendido: PATCH status=vendido (mantém o histórico e a página). Saiu da carteira: DELETE, que faz soft delete.

    5

    Reconciliação periódica

    Entre as rodadas, use GET /properties?updated_since=… para puxar só o que mudou. Uma vez por dia, faça a varredura completa paginada e compare com o seu inventário: crie o que falta, atualize divergências, remova o que saiu. É a rede de segurança para eventos perdidos.

    Reconciliação paginada (bash)
    KEY="av_live_..."
    URL="https://altovaleshopping.com/api/v1"
    PAGE=1
    while :; do
      RES=$(curl -sS "$URL/properties?page=$PAGE&limit=100" -H "X-API-Key: $KEY")
      echo "$RES" | jq -r '.data[] | [.id, .reference_code, .status, .price] | @tsv'
      TOTAL=$(echo "$RES" | jq -r .total)
      (( PAGE * 100 >= TOTAL )) && break
      PAGE=$((PAGE + 1))
      sleep 1   # respeita o rate limit
    done

    8. Fotos da galeria

    Não há upload binário. Você envia URLs públicas e o servidor baixa, valida e re-hospeda cada imagem — o download sai do nosso lado, com o Referer correto para CDNs que bloqueiam hotlink. Até 50 URLs por requisição.

    Substituir a galeria inteira
    curl -X POST 'https://altovaleshopping.com/api/v1/properties/UUID/images' \
      -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
      -d '{
        "source_urls": ["https://cdn.seucrm.com/1.jpg", "https://cdn.seucrm.com/2.jpg"],
        "replace": true
      }'

    Com replace: true a troca é protegida: as fotos novas entram em posições provisórias e a galeria antiga continua servindo o anúncio durante os downloads. A substituição só é confirmada se a galeria nova ficar maior que a atual — se o seu CDN falhar no meio de 30 fotos e só 2 chegarem, o anúncio permanece com as 30 e a resposta traz replaced: false com o motivo.

    Uma URL é ignorada (contabilizada em skipped, sem derrubar a requisição) quando a resposta não é 2xx, o Content-Type não é de imagem, o arquivo é menor que 5 KB (pixel de tracking), tem menos de 500px nos dois eixos (logo de imobiliária) ou passa de 15 MB.

    Segurança de SSRF: aceitamos apenas http/https para hosts públicos. Localhost, loopback, faixas privadas, CGNAT, link-local (incluindo endpoints de metadados de nuvem), IPv6 literal e domínios .internal são recusados.

    9. Webhooks (tempo real)

    A API cobre a saída (você publica). Os webhooks cobrem a entrada: leads, visitas, propostas e mensagens geradas no marketplace chegam ao seu sistema em segundos, sem polling.

    1

    Cadastre a URL

    Painel → Configurações → aba IntegraçõesWebhooks. Informe um endpoint HTTPS público e selecione os eventos.

    2

    Defina um secret

    Com secret configurado, cada entrega leva o header X-Webhook-Signature — HMAC SHA-256 do corpo bruto, em hexadecimal. Valide sempre antes de processar.

    3

    Responda 2xx rápido

    O timeout é de 10 segundos e fazemos 2 tentativas (a segunda 1s depois). Enfileire o processamento e responda imediatamente. O histórico de entregas com status e corpo da resposta fica visível no painel.

    Eventos disponíveis

    property.createdproperty.updatedproperty.activatedproperty.soldlead.createdvisit.requestedvisit.confirmedoffer.receivedoffer.acceptedoffer.rejectedmessage.received
    Corpo da entrega (POST no seu endpoint)
    {
      "event": "lead.created",
      "payload": { "...": "dados do evento" },
      "timestamp": "2026-08-10T12:34:56.000Z",
      "delivery_id": "uuid"
    }
    
    // Headers
    // X-Webhook-Event: lead.created
    // X-Webhook-Delivery: uuid
    // X-Webhook-Signature: <hmac-sha256-hex do corpo, se houver secret>
    Validando a assinatura (Node.js)
    import crypto from "node:crypto";
    
    app.post("/webhooks/avs", express.raw({ type: "application/json" }), (req, res) => {
      const expected = crypto
        .createHmac("sha256", process.env.AVS_WEBHOOK_SECRET!)
        .update(req.body)              // corpo BRUTO, antes do JSON.parse
        .digest("hex");
    
      const received = req.header("X-Webhook-Signature") ?? "";
      const ok =
        received.length === expected.length &&
        crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
    
      if (!ok) return res.sendStatus(401);
    
      res.sendStatus(200);           // responde primeiro
      enqueue(JSON.parse(req.body)); // processa depois
    });
    Deduplique por delivery_id: a retentativa reenvia o mesmo id, e uma resposta lenta pode gerar entrega repetida do mesmo evento.

    10. Erros e retentativas

    Toda resposta de erro tem o mesmo formato: { "error": "mensagem" }.

    CódigoCausa e ação
    400JSON inválido, título ausente ou nenhum campo gravável no corpo. Não retente sem corrigir.
    401Chave ausente, inválida ou revogada. Confira o header X-API-Key.
    403Escopo insuficiente — a chave não tem properties:write.
    404Recurso ou imóvel inexistente, ou pertencente a outra conta.
    405Método não suportado no path (ex.: PATCH sem :id).
    429Rate limit estourado. Aguarde até 60s e refaça.
    500Erro interno. Retente com backoff exponencial; se persistir, contate o suporte.
    Política de retentativa recomendada
    async function callApi(path: string, init: RequestInit = {}) {
      for (let attempt = 0; attempt < 3; attempt++) {
        const r = await fetch(`${API}${path}`, {
          ...init,
          headers: { "X-API-Key": KEY, "Content-Type": "application/json", ...init.headers },
        });
    
        if (r.ok) return r.json();
    
        if (r.status === 429) {                        // limite: espera crescente
          await sleep(1000 * (attempt + 1));
          continue;
        }
        if (r.status >= 500) {                         // servidor: backoff exponencial
          await sleep(500 * 2 ** attempt);
          continue;
        }
        throw new Error(`API ${r.status}: ${(await r.json()).error}`);  // 4xx: não retentar
      }
      throw new Error("Máximo de tentativas excedido");
    }

    11. Segurança e rotação

    • Servidor apenas. A chave nunca deve sair do seu back-end — nada de front-end, app mobile ou repositório. Requisições do navegador expõem a chave a qualquer visitante.
    • Uma chave por sistema, com o menor escopo possível. Facilita auditoria e revogação cirúrgica.
    • Rotação a cada 90 dias, ou imediata em caso de suspeita: gere a nova, atualize o consumidor, revogue a antiga. Em planos com limite de 1 chave, a criação já revoga a anterior — troque em janela de manutenção.
    • Monitore o uso. A tela de API Keys mostra o último uso de cada chave. Uso inesperado (ou ausência de uso) é sinal para revogar.
    • Dados de contato de leads recebidos por webhook são dados pessoais sob a LGPD. Use apenas para a finalidade do atendimento imobiliário e respeite as solicitações de exclusão.

    12. Checklist de homologação

    Antes de ligar a integração em produção, confirme:

    A chave está em cofre/variável de ambiente, fora do repositório.

    O escopo da chave é o mínimo necessário.

    Existe mapeamento persistido entre o seu ID e o id da plataforma.

    Retentativas tratam 429 e 5xx; 4xx não são retentados em loop.

    As chamadas em lote respeitam o limite por minuto (~1s entre requisições).

    Fotos são enviadas antes de publicar o imóvel (status=ativo).

    O endpoint de webhook valida X-Webhook-Signature e responde 2xx em menos de 10s.

    Entregas repetidas são deduplicadas por delivery_id.

    Há alarme/log do seu lado para falhas de sincronização.

    Suporte

    Dúvidas de integração, aumento de rate limit, acesso a recursos ainda não expostos ou relato de incidente: [email protected]. Inclua o prefixo da chave (nunca a chave inteira), o horário aproximado e o corpo da resposta recebida.

    Versão 1Última revisão: 10/08/2026