Pagar por posición
GET /property-v1/properties/{paId}/publication/{portal}/positioning
PUT /property-v1/properties/{paId}/publication/{portal}/positioning
Algunos portales dejan pagar para que un aviso aparezca más arriba en el listado. El GET te da todo
lo que hace falta para decidir cuánto; el PUT lo ejecuta.
Es un paso aparte de publicar. Un aviso se publica igual sin esto; lo que cambia es dónde aparece.
Cada portal lo cobra distinto
El campo mechanism te dice qué esperar, así que un mismo código sirve para todos:
| Portal | Qué se paga | mechanism | spend es |
|---|---|---|---|
| Argenprop | puntos de un abono mensual, que la inmobiliaria reparte entre sus avisos | points | un número |
| Zonaprop | el plan del aviso | tier | el nombre de un nivel |
| MercadoLibre | el tipo de publicación | tier | el nombre de un nivel |
| Cabaprop | — no cobra por posición | — | — |
Hoy sólo Argenprop está habilitado. Los otros reciben 501, y el mensaje distingue los dos
casos: "el portal no tiene posicionamiento pagado" no es lo mismo que "todavía no lo tenemos
habilitado por acá".
El GET: el contexto para decidir
GET …/positioning?simulate=5,100&visits=1-10
| Parámetro | Qué hace | Default |
|---|---|---|
simulate | lista de valores a simular: "¿en qué posición quedaría si gasto esto?" | vacío |
visits | rango de posiciones para traer cuántas visitas recibe cada una | vacío |
simulate cuesta una llamada de la cuota de tu cliente POR CADA VALOR, y visits cuesta dos.
Por eso no vienen por default: los pedís cuando los necesitás. Hasta 10 valores en simulate y
25 posiciones en visits.
Qué te contesta
{ "portal": "argenpropapi",
"mechanism": "points",
"paId": "01M5375T7YS6PC223DWC0YQPGS",
"current": { "position": 794, "byBedrooms": 310, "indexed": true },
"bounds": { "min": 0, "max": 94518 },
"balance": { "available": 10, "total": 10, "hasSubscription": true,
"asOf": "2026-10-16T21:10:00Z" },
"segment": { "propertyType": 1, "operation": 1, "locality": 2102,
"neighborhood": 20, "bedrooms": 2 },
"simulation": [ { "spend": 5, "position": 332 }, { "spend": 100, "position": 185 } ],
"visitsByPosition": [ { "position": 1, "visits": 7991 }, { "position": 2, "visits": 3673 } ],
"thresholds": [ { "position": 1, "points": 90018 }, { "position": 2, "points": 90017 } ],
"calls": 9 }
| Campo | Qué es |
|---|---|
current | Dónde está el aviso hoy. indexed: false significa que el portal todavía no lo indexó |
bounds | El piso y el techo de lo que podés gastar en este aviso |
balance | Lo que le queda a tu cliente, con la fecha en que lo leímos |
segment | Contra qué conjunto de avisos se está comparando |
simulation | Una entrada por cada valor que pediste |
visitsByPosition | Cuántas visitas recibe cada posición |
thresholds | Cuántos puntos tiene quien está en cada posición |
calls | Cuántas llamadas a la cuota de tu cliente costó esta consulta |
🔴 El dato que hace útil a todo lo demás
visitsByPosition cruzado con simulation te dice cuántas visitas compra cada punto:
posición 1 → 7991 visitas con 5 puntos → posición 332
posición 2 → 3673 visitas con 100 puntos → posición 185
La posición 2 recibe menos de la mitad que la 1. Y la curva no es lineal: los primeros 5 puntos movieron 462 posiciones, los 5 siguientes movieron 22.
🔴 Lo que no pudimos averiguar te lo decimos
Un bloque que no se pudo leer no viene en null: no viene, y su motivo está en unavailable.
{ "current": { "position": 794, "indexed": true },
"balance": { "available": 10, "asOf": "…" },
"unavailable": [
{ "block": "simulation", "reason": "no_indexado" },
{ "block": "visitsByPosition", "reason": "no_indexado" } ] }
La diferencia importa: un campo que existe y dice null significaría "el portal dice que no hay
nada". Un campo ausente con su motivo significa "no pudimos averiguarlo", que es lo que pasó.
reason | Qué pasó | Qué hacer |
|---|---|---|
no_indexado | el aviso se publicó hace muy poco y el portal todavía no lo indexó | esperar y volver a pedir. No es un error |
segmento_sin_datos_suficientes | el mercado de esa propiedad es muy chico para que el portal tenga estadísticas | nada: no hay números honestos que dar |
segmento_incompleto | falta la ubicación de la propiedad | completar la zona y republicar |
| un código del portal | el portal rechazó esa lectura | leelo: es el mismo tipo de código que en el publish |
current y el PUT siguen funcionando aunque falte todo lo demás. Las estadísticas y la
simulación salen de una parte del portal que puede no estar disponible; publicar, consultar tu
posición y asignar no dependen de ella.
Si el segmento se ensanchó, te lo decimos
Cuando el segmento de tu propiedad tiene pocos avisos, el portal compara contra uno más amplio. Si eso pasa, lo declaramos:
{ "segment": { "propertyType": 1, "operation": 1, "locality": 2102,
"neighborhood": 20, "bedrooms": null },
"segmentWidened": ["bedrooms"] }
Rotulá el dato con el segmento que viene, no con el que pediste. Una curva de "Palermo, cualquier cantidad de dormitorios" no es la misma que una de "Palermo, 2 dormitorios".
El PUT: asignar
{ "spend": 50 }
{ "portal": "argenpropapi", "mechanism": "points",
"paId": "01M5375T7YS6PC223DWC0YQPGS",
"spend": 50, "position": 185, "calls": 3 }
position es la posición REAL que leímos, no la que la simulación prometía: la consultamos después
de asignar.
Pero el portal tarda en recalcularla. Medido: asignando 5 puntos la simulación daba posición 463
y la lectura inmediata seguía devolviendo la anterior. No es un error y no significa que no se
aplicó — los puntos ya se descontaron del saldo. Si querés mostrar el efecto, volvé a pedir el
GET un rato después; si querés mostrarlo al instante, usá el número de la simulación y decí que
es una estimación.
spend: 0 es válido — es "dejá de pagar". Por eso no hay un DELETE acá: dar de baja la
publicación es otra cosa.
Si el portal todavía no reindexó, position no viene y te lo decimos:
{ "spend": 50, "positionUnavailable": "el portal todavía no reindexó el aviso" }
No validamos tu spend contra el max que leímos. Ese número viene de una lectura que puede
estar vieja, y rechazarte por eso sería que nuestra copia decida en lugar del portal. El portal es
la autoridad sobre lo que acepta: si lo rechaza, te devolvemos su motivo.
Errores
| Código | Qué pasó |
|---|---|
400 | Un parámetro mal formado. Se valida antes de llamar al portal, así que no te gasta cuota |
409 portal_not_connected | Esa cuenta no está conectada al portal |
409 no_publicado | La propiedad no está publicada ahí: no hay posición que consultar |
404 | Esa propiedad no está en tu inventario |
501 | Ese portal no tiene posicionamiento pagado habilitado acá |
502 portal_rechazo | El portal rechazó la asignación. reason trae su código |
Relacionado
- Publicar en un portal — esto es un paso posterior
- Consultar el estado en el portal — "¿está publicado?", que es otra pregunta
- Los estados de la propiedad — reservada, vendida, alquilada
- La cuota de tu cliente — cómo se gasta