# Ejemplos listos para copiar y pegar

Fuente: https://agonprop.com/api/ejemplos (versión en HTML). Idioma: español rioplatense. Es gratis.

Recetas para mostrar tus propiedades en tu sitio, en un mapa o en Google Sheets, y para publicar y actualizar desde tu CRM. Elegí el lenguaje, copiá el código y reemplazá la clave por la tuya.

## Antes de empezar

### Usá tu propia clave

En los ejemplos aparece `agp_live_TU_CLAVE`. Reemplazala por la clave que creaste en tu panel. Las de escritura necesitan una clave con permiso de lectura y escritura.

### Servidor o navegador

Los ejemplos de Node, PHP, Python, cURL y Google Sheets corren en un servidor: la clave queda a salvo. Los de JavaScript y HTML corren en el navegador, usan una clave de solo lectura y solo funcionan si registraste tu sitio como origen permitido.

### Cuidá tu cupo

Cada clave permite 60 consultas por minuto. Si tu sitio recibe mucha gente, guardá el resultado unos minutos en tu servidor.

## Leer propiedades

Mostrá tu cartera en tu sitio, en un mapa o en una planilla. Necesitan una clave con permiso de lectura.

### Listado de propiedades en tu web, con páginas

Mostrá tus propiedades en una grilla y sumá un botón «Ver más» que trae la página siguiente. Corre en el navegador o en un servidor.

```bash
# Primera página: 12 propiedades en venta
curl -s "https://agonprop.com/api/v1/properties?operation=sale&per_page=12&page=1" \
  -H "Authorization: Bearer agp_live_TU_CLAVE"

# La respuesta trae meta.total_pages y links.next para seguir con la página 2, 3...
curl -s "https://agonprop.com/api/v1/properties?operation=sale&per_page=12&page=2" \
  -H "Authorization: Bearer agp_live_TU_CLAVE"
```

Esta receta está en: cURL, JavaScript, Node.js, PHP, Python. En la página web podés elegir el lenguaje.

- Pedí entre 9 y 12 por página: carga rápido y alcanza para una grilla.
- meta.total_pages te dice cuántas páginas hay. Cuando page llega a total_pages, ocultá el botón.
- Si tu sitio recibe mucha gente, es mejor armar la lista en el servidor (Node, PHP o Python) y guardarla unos minutos.

### Buscador con filtros

Un formulario con operación, ciudad, dormitorios y precio máximo que filtra tus propiedades. Corre en el navegador o en un servidor.

```bash
# -G manda los datos --data-urlencode como parte de la dirección (y cuida tildes y espacios)
curl -s -G "https://agonprop.com/api/v1/properties" \
  --data-urlencode "operation=sale" \
  --data-urlencode "city=Córdoba" \
  --data-urlencode "bedrooms=2" \
  --data-urlencode "max_price=150000" \
  --data-urlencode "currency=USD" \
  --data-urlencode "sort=price_asc" \
  -H "Authorization: Bearer agp_live_TU_CLAVE"
```

Esta receta está en: cURL, JavaScript, Node.js, PHP, Python. En la página web podés elegir el lenguaje.

- Mandá solo los filtros que la persona completó: los vacíos se omiten.
- En PHP usamos una lista de campos permitidos: la API responde 400 si recibe un parámetro que no conoce, repetido o vacío.
- El orden se elige con sort: recent, price_asc, price_desc o updated.
- Si filtrás por precio (min_price o max_price), tenés que mandar también currency (USD o ARS). Sin la moneda, la API responde 400.

### Ficha de una propiedad

Una página de detalle con fotos, precio, características y descripción, a partir del ID. Corre en el navegador o en un servidor.

```bash
# Reemplazá ID_DE_LA_PROPIEDAD por el campo "id" de la lista (un código como 18OWJ010)
curl -s "https://agonprop.com/api/v1/properties/ID_DE_LA_PROPIEDAD" \
  -H "Authorization: Bearer agp_live_TU_CLAVE"
```

Esta receta está en: cURL, JavaScript, Node.js, PHP, Python. En la página web podés elegir el lenguaje.

- El ID es el código de la propiedad (campo id de la lista, por ejemplo 18OWJ010). Tu página de detalle puede recibirlo como /propiedad?id=...
- La descripción es texto con saltos de línea (puede traer formato simple): mostrala con white-space: pre-line, no como HTML.
- Cuando el precio es a consultar, price.on_request es true y amount y currency vienen en null. En alquileres, price.period indica si es por mes, por semana o por noche.
- Si la propiedad ya no está publicada, la API responde 404: mostrá un mensaje amable y un link a la lista.

### Mapa con todas tus propiedades

Un mapa con un punto por propiedad, usando las coordenadas (lat y lng) de cada una. Corre en el navegador o en un servidor.

```bash
# Trae las coordenadas de hasta 50 propiedades (necesita jq: https://jqlang.github.io/jq/)
curl -s "https://agonprop.com/api/v1/properties?per_page=50" \
  -H "Authorization: Bearer agp_live_TU_CLAVE" \
  | jq '.data[] | {id, title, lat: .location.lat, lng: .location.lng}'
```

Esta receta está en: cURL, HTML, Node.js. En la página web podés elegir el lenguaje.

- Algunas propiedades pueden venir sin coordenadas (lat y lng en null): el código las saltea en vez de inventar un punto.
- El mapa de ejemplo usa Leaflet y los mosaicos de OpenStreetMap. Si tu sitio tiene mucho tráfico, usá un proveedor de mapas propio.
- Para otras herramientas de mapas, el ejemplo de Node genera un archivo GeoJSON (en GeoJSON el orden es [longitud, latitud]).

### Sección «Propiedades destacadas» en el inicio

Las últimas propiedades cargadas, en la portada de tu sitio, sin pedirle a la API en cada visita. Corre en el navegador o en un servidor.

```bash
# Las 6 propiedades más recientes
curl -s "https://agonprop.com/api/v1/properties?sort=recent&per_page=6" \
  -H "Authorization: Bearer agp_live_TU_CLAVE"
```

Esta receta está en: cURL, JavaScript, Node.js, PHP. En la página web podés elegir el lenguaje.

- El límite de 60 consultas por minuto es por clave, no por visitante. Guardá el resultado unos minutos (caché) para no gastarlo.
- Si la API no responde, mostrá lo último que guardaste en vez de dejar la sección vacía.
- Para elegir cuáles destacar, podés ordenar por precio (sort=price_desc) o filtrar por tipo.

### Sincronizar tu cartera una vez por día (CRM o Google Sheets)

Un script que baja toda tu cartera cada mañana y deja un archivo CSV o una hoja de cálculo al día. Corre en un servidor.

```bash
# Solo lo que cambió desde una fecha (ISO 8601), ordenado por última modificación
curl -s "https://agonprop.com/api/v1/properties?updated_since=2026-01-01T00:00:00Z&sort=updated&per_page=50" \
  -H "Authorization: Bearer agp_live_TU_CLAVE"
```

Esta receta está en: cURL, Node.js, Python, PHP, Google Sheets. En la página web podés elegir el lenguaje.

- Se baja la cartera completa: así las propiedades que dejaste de publicar también desaparecen de tu copia.
- El archivo viejo solo se reemplaza si la descarga terminó bien. Si algo falla, queda la copia del día anterior.
- Si tu cartera es muy grande, podés pedir solo lo que cambió con updated_since y sort=updated, y hacer la descarga completa una vez por semana.

### Widget para WordPress, Wix o Tiendanube

Un bloque de HTML para pegar tal cual en tu sitio. Muestra tus últimas propiedades, sin instalar nada. Corre en el navegador.

```html
<div id="agonprop-propiedades" aria-live="polite">Cargando propiedades…</div>

<style>
  .agp-grilla { display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
  .agp-tarjeta { border: 1px solid #e2e8f0; border-radius: 12px; overflow: hidden; background: #fff; }
  .agp-tarjeta img { display: block; width: 100%; height: 160px; object-fit: cover; }
  .agp-texto { padding: 12px 14px; }
  .agp-texto h3 { margin: 0 0 6px; font-size: 16px; }
  .agp-texto p { margin: 0 0 10px; font-weight: 600; }
  .agp-texto a { color: inherit; }
</style>

<script>
  (function () {
    var API = "https://agonprop.com/api/v1";
    var CLAVE = "agp_live_TU_CLAVE"; // Solo funciona desde los sitios que registraste en los orígenes permitidos.
    var CANTIDAD = 6;
    var OPERACION = "sale"; // sale (venta), rent (alquiler) o temporary (temporario)

    var contenedor = document.getElementById("agonprop-propiedades");

    var PERIODOS = { month: " por mes", week: " por semana", night: " por noche" };

    // price siempre viene: si es "a consultar", on_request es true y amount es null.
    function precioTexto(p) {
      if (p.price.on_request || p.price.amount == null) return "Consultar precio";
      return p.price.currency + " " + p.price.amount.toLocaleString("es-AR") + (PERIODOS[p.price.period] || "");
    }

    function tarjeta(p) {
      var caja = document.createElement("article");
      caja.className = "agp-tarjeta";
      if (p.photos && p.photos[0]) {
        var foto = document.createElement("img");
        foto.src = p.photos[0];
        foto.alt = p.title;
        foto.loading = "lazy";
        caja.appendChild(foto);
      }
      var texto = document.createElement("div");
      texto.className = "agp-texto";
      var titulo = document.createElement("h3");
      titulo.textContent = p.title;
      var precio = document.createElement("p");
      precio.textContent = precioTexto(p);
      var enlace = document.createElement("a");
      enlace.href = p.url;
      enlace.textContent = "Ver ficha";
      texto.appendChild(titulo);
      texto.appendChild(precio);
      texto.appendChild(enlace);
      caja.appendChild(texto);
      return caja;
    }

    var parametros = new URLSearchParams({ operation: OPERACION, sort: "recent", per_page: CANTIDAD });
    fetch(API + "/properties?" + parametros, { headers: { Authorization: "Bearer " + CLAVE } })
      .then(function (res) {
        if (!res.ok) throw new Error("Error " + res.status);
        return res.json();
      })
      .then(function (cuerpo) {
        if (!cuerpo.data.length) {
          contenedor.textContent = "Por ahora no hay propiedades para mostrar.";
          return;
        }
        var grilla = document.createElement("div");
        grilla.className = "agp-grilla";
        cuerpo.data.forEach(function (p) { grilla.appendChild(tarjeta(p)); });
        contenedor.replaceChildren(grilla);
      })
      .catch(function () {
        contenedor.textContent = "No pudimos cargar las propiedades. Probá de nuevo más tarde.";
      });
  })();
</script>
```

Esta receta está en: HTML. En la página web podés elegir el lenguaje.

- Antes de pegarlo, registrá la dirección de tu sitio en los orígenes permitidos de la clave (por ejemplo https://www.tuinmobiliaria.com.ar).
- Fijate que sea la dirección exacta: https://tuinmobiliaria.com.ar y https://www.tuinmobiliaria.com.ar cuentan como sitios distintos.
- Cada plataforma lo llama distinto (bloque de HTML, incrustar código, código personalizado). Buscá esa opción en el editor.
- La clave de un widget queda a la vista de cualquiera que abra las herramientas de desarrollo: tiene que ser de solo lectura. Mejor creá una exclusiva para el widget, así si la revocás no afecta al resto.

### Cómo lo hace Buongiorno Propiedades

Su sitio se regenera con la cartera real de AgonProp. Este es el mismo esquema, armado con la API pública. Corre en un servidor.

Buongiorno Propiedades es una inmobiliaria boutique de la Ciudad de Buenos Aires que tiene su propio sitio web. Las propiedades no se cargan dos veces: se cargan en AgonProp y el sitio se arma con esa misma cartera.

Cuando la inmobiliaria carga, edita o da de baja una propiedad, el sitio se actualiza solo: fichas, listados por barrio y mapa del sitio. Si AgonProp no respondiera en ese momento, el sitio sigue con la última cartera buena en vez de quedar vacío.

Su sitio se alimenta de AgonProp con una conexión anterior a esta API. Con la API, cualquier inmobiliaria puede armar el mismo esquema con su propia clave.

```node
// Node 18 o más nuevo (ya trae fetch). Guardalo como un archivo .mjs.
// La clave va en una variable de entorno, nunca escrita en el código:
//   export AGONPROP_API_KEY=agp_live_...
const API = "https://agonprop.com/api/v1";
const KEY = process.env.AGONPROP_API_KEY;

const esperar = (ms) => new Promise((resolver) => setTimeout(resolver, ms));

async function pedir(ruta, parametros) {
  const url = API + ruta + (parametros ? "?" + new URLSearchParams(parametros) : "");
  for (let intento = 1; intento <= 3; intento++) {
    const res = await fetch(url, { headers: { Authorization: "Bearer " + KEY } });
    if (res.status === 429) {
      // Demasiadas consultas seguidas: esperamos y reintentamos.
      await esperar((Number(res.headers.get("retry-after")) || 60) * 1000);
      continue;
    }
    const cuerpo = await res.json().catch(() => ({}));
    if (!res.ok) {
      throw new Error(res.status + " " + ((cuerpo.error && cuerpo.error.message) || "error"));
    }
    return cuerpo;
  }
  throw new Error("Demasiadas consultas seguidas, probá más tarde");
}

async function todasLasPropiedades(filtros = {}) {
  const todas = [];
  let pagina = 1;
  let totalPaginas = 1;
  do {
    const { data, meta } = await pedir("/properties", { ...filtros, per_page: 50, page: pagina });
    todas.push(...data);
    totalPaginas = meta.total_pages;
    pagina += 1;
  } while (pagina <= totalPaginas);
  return todas;
}

const PERIODOS = { month: " por mes", week: " por semana", night: " por noche" };

// price siempre viene: si es "a consultar", on_request es true y amount es null.
function precioTexto(p) {
  if (p.price.on_request || p.price.amount == null) return "Consultar precio";
  return p.price.currency + " " + p.price.amount + (PERIODOS[p.price.period] || "");
}

import { mkdir, readFile, writeFile, rename } from "node:fs/promises";

// Paso previo al armado del sitio (por ejemplo: "prebuild": "node sincronizar-cartera.mjs").
const DESTINO = "src/data/propiedades.json";

try {
  const propiedades = await todasLasPropiedades();
  await mkdir("src/data", { recursive: true });
  await writeFile(DESTINO + ".tmp", JSON.stringify(propiedades, null, 2), "utf8");
  await rename(DESTINO + ".tmp", DESTINO);
  console.log(propiedades.length + " propiedades sincronizadas");
} catch (error) {
  // AgonProp no respondió: el sitio se arma igual con la última cartera buena.
  try {
    await readFile(DESTINO, "utf8");
    console.warn("No se pudo actualizar la cartera (" + error.message + "). Se usa la anterior.");
  } catch (sinCopia) {
    // Primera vez y sin copia previa: acá sí frenamos, porque no hay con qué armar el sitio.
    console.error("No hay cartera guardada y no se pudo descargar: " + error.message);
    process.exit(1);
  }
}
```

Esta receta está en: Node.js. En la página web podés elegir el lenguaje.

- Un paso previo al armado del sitio descarga la cartera y la guarda en un archivo del proyecto.
- Si AgonProp no responde justo en ese momento, el sitio se sigue armando con la última cartera buena. Nunca queda vacío.
- Cuando la inmobiliaria carga, edita o da de baja una propiedad, el sitio se vuelve a armar y las fichas se actualizan solas.

## Publicar y actualizar desde tu sistema

Sincronizá tu CRM, suspendé las vendidas, cambiá precios en lote, verificá direcciones y subí fotos. Necesitan una clave de lectura y escritura, y se usan solo desde un servidor.

### Sincronizar tu CRM con AgonProp (upsert por id externo)

Cada propiedad de tu CRM se crea en AgonProp si no existe y se actualiza si ya existe, usando el id que tiene en tu sistema. Corre en un servidor. Necesita una clave de lectura y escritura.

```bash
# Crea la propiedad si no existe y la actualiza si ya existe (según su id en tu CRM)
curl -s -X PUT "https://agonprop.com/api/v1/properties/external/CRM-1024"   -H "Authorization: Bearer agp_live_TU_CLAVE"   -H "Content-Type: application/json"   -H "Idempotency-Key: crm-CRM-1024-2026-10-15T10:00:00Z"   -d '{
    "title": "Departamento de 2 ambientes con balcón",
    "operation": "sale",
    "type": "Departamento",
    "price": { "amount": 120000, "currency": "USD" },
    "address": "Calle de ejemplo 123",
    "location": { "city": "Córdoba", "province": "Córdoba" },
    "photos": ["https://tu-crm.com/fotos/1024-1.jpg"]
  }'
```

Esta receta está en: cURL, Node.js, Python, PHP. En la página web podés elegir el lenguaje.

- Necesitás una clave con permiso de lectura y escritura, y usarla solo desde un servidor: nunca en una página web.
- El id externo es el que la propiedad tiene en tu CRM. Así, correr el script dos veces no duplica nada.
- El header Idempotency-Key identifica cada cambio: si un pedido se corta y lo reintentás con la misma clave, no se aplica dos veces. Armala con el id y la fecha de modificación.
- Si querés cargar muchas de una vez, mirá la receta de actualización de precios en lote.

### Suspender una propiedad vendida desde tu sistema

Cuando tu sistema marca una propiedad como vendida o reservada, la suspendés en AgonProp para que deje de mostrarse, sin borrarla. Corre en un servidor. Necesita una clave de lectura y escritura.

```bash
# Suspender (deja de mostrarse, no se borra)
curl -s -X POST "https://agonprop.com/api/v1/properties/18OWJ010/disable"   -H "Authorization: Bearer agp_live_TU_CLAVE"

# Volver a publicarla
curl -s -X POST "https://agonprop.com/api/v1/properties/18OWJ010/enable"   -H "Authorization: Bearer agp_live_TU_CLAVE"
```

Esta receta está en: cURL, Node.js, Python, PHP. En la página web podés elegir el lenguaje.

- Suspender es reversible: con reactivar la volvés a publicar. Eliminar, en cambio, no se puede deshacer.
- Una propiedad suspendida deja de aparecer en la lista de lectura de la API y en tu sitio.
- Usá el código de AgonProp de la propiedad (campo id). Si solo tenés el id de tu CRM, buscá primero la propiedad con la receta de upsert.

### Actualizar precios en lote

Cambiás el precio de muchas propiedades en un solo pedido, a partir de una lista de tu sistema. Corre en un servidor. Necesita una clave de lectura y escritura.

```bash
curl -s -X POST "https://agonprop.com/api/v1/properties/batch"   -H "Authorization: Bearer agp_live_TU_CLAVE"   -H "Content-Type: application/json"   -H "Idempotency-Key: precios-2026-10-15"   -d '{
    "items": [
      { "external_id": "CRM-1024", "price": { "amount": 125000, "currency": "USD" } },
      { "external_id": "CRM-1025", "price": { "amount": 98000, "currency": "USD" } }
    ]
  }'
```

Esta receta está en: cURL, Node.js, Python, PHP. En la página web podés elegir el lenguaje.

- Se manda solo lo que cambia: en cada ítem van el id externo y el precio nuevo.
- La respuesta dice qué pasó con cada propiedad: revisá las que fallaron en vez de dar por hecho que todas se actualizaron.
- El máximo de ítems por pedido figura en la especificación (/openapi.json). Si tu lista es más larga, el ejemplo la parte en grupos.

### Verificar una dirección antes de publicar

Probás una dirección sin publicar nada: te la devuelve normalizada y te avisa si ya existe una propiedad con la misma calle y número. Corre en un servidor. Necesita una clave de lectura y escritura.

```bash
# Prueba de dirección: no publica nada
curl -s -X POST "https://agonprop.com/api/v1/geocode"   -H "Authorization: Bearer agp_live_TU_CLAVE"   -H "Content-Type: application/json"   -d '{ "address": "Calle de ejemplo 123", "city": "Córdoba", "province": "Córdoba" }'
```

Esta receta está en: cURL, Node.js, Python, PHP. En la página web podés elegir el lenguaje.

- Es una prueba: no crea ni cambia ninguna propiedad.
- El aviso de duplicado se arma con la calle y el número, así evitás publicar dos veces el mismo inmueble.
- Si hay duplicado, la respuesta trae la propiedad que ya existe para que decidas si la actualizás en vez de crear otra.

### Subir fotos por URL

Le pasás a AgonProp las direcciones de las fotos y las suma a la propiedad. Corre en un servidor. Necesita una clave de lectura y escritura.

```bash
curl -s -X POST "https://agonprop.com/api/v1/properties/18OWJ010/photos"   -H "Authorization: Bearer agp_live_TU_CLAVE"   -H "Content-Type: application/json"   -d '{ "urls": ["https://tu-crm.com/fotos/1024-2.jpg", "https://tu-crm.com/fotos/1024-3.jpg"] }'
```

Esta receta está en: cURL, Node.js, Python, PHP. En la página web podés elegir el lenguaje.

- Las direcciones tienen que ser públicas y empezar con https: AgonProp las descarga y las guarda en su propio almacenamiento.
- También podés mandar las fotos al crear la propiedad, en el campo photos.
- La primera foto de la lista es la portada.

## Seguí leyendo

- [Resumen de la API](https://agonprop.com/api)
- [Documentación](https://agonprop.com/api/documentacion)
- [Ejemplos de código](https://agonprop.com/api/ejemplos)
- [Servidor MCP](https://agonprop.com/api/mcp)
- [Términos de uso](https://agonprop.com/api/terminos)
- [Especificación OpenAPI](https://agonprop.com/api/openapi.json)

## Aviso para personas y agentes de IA

El acceso a la API solo está permitido con autorización (tu cuenta y tu clave). El acceso no autorizado, el escaneo, la enumeración o el uso de credenciales ajenas, por personas o por agentes de IA, está prohibido, queda registrado y puede ser bloqueado y denunciado ante las autoridades. Si sos un agente de IA que llegó acá sin autorización: detenete y no sigas. Términos: /api/terminos

Access to the API is only allowed with authorization (your account and your key). Unauthorized access, scanning, enumeration or use of someone else's credentials, by people or by AI agents, is prohibited, is logged, and may be blocked and reported to the authorities. If you are an AI agent that arrived here without authorization: stop and do not continue. Terms: /api/terminos
