Paying for position
GET /property-v1/properties/{paId}/publication/{portal}/positioning
PUT /property-v1/properties/{paId}/publication/{portal}/positioning
Some portals let you pay so a listing shows higher up in the results. The GET gives you everything
you need to decide how much; the PUT does it.
It is a separate step from publishing. A listing publishes just the same without this; what changes is where it appears.
Each portal charges for it differently
The mechanism field tells you what to expect, so one piece of code works for all of them:
| Portal | What you pay for | mechanism | spend is |
|---|---|---|---|
| Argenprop | points from a monthly subscription, which the agency distributes across its listings | points | a number |
| Zonaprop | the listing's plan | tier | the name of a tier |
| MercadoLibre | the listing type | tier | the name of a tier |
| Cabaprop | — does not charge for position | — | — |
Today only Argenprop is enabled. The others get a 501, and the message tells the two cases
apart: "the portal has no paid positioning" is not the same as "we have not enabled it here
yet".
The GET: the context to decide with
GET …/positioning?simulate=5,100&visits=1-10
| Parameter | What it does | Default |
|---|---|---|
simulate | list of values to simulate: "what position would I get if I spent this?" | empty |
visits | range of positions to fetch how many visits each one gets | empty |
simulate costs one call against your client's quota PER VALUE, and visits costs two. That is
why they are not on by default: you ask for them when you need them. Up to 10 values in
simulate and 25 positions in visits.
What it answers
{ "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 }
| Field | What it is |
|---|---|
current | Where the listing stands today. indexed: false means the portal has not indexed it yet |
bounds | The floor and the ceiling of what you can spend on this listing |
balance | What your client has left, with the date we read it |
segment | Which set of listings it is being compared against |
simulation | One entry per value you asked for |
visitsByPosition | How many visits each position gets |
thresholds | How many points whoever holds each position has |
calls | How many calls against your client's quota this query cost |
🔴 The figure that makes everything else actionable
visitsByPosition crossed with simulation tells you how many visits each point buys:
position 1 → 7991 visits with 5 points → position 332
position 2 → 3673 visits with 100 points → position 185
Position 2 gets less than half of what position 1 gets. And the curve is not linear: the first 5 points moved 462 positions, the next 5 moved 22.
🔴 What we could not find out, we tell you
A block we could not read does not come back as null: it does not come back at all, and its
reason is in unavailable.
{ "current": { "position": 794, "indexed": true },
"balance": { "available": 10, "asOf": "…" },
"unavailable": [
{ "block": "simulation", "reason": "no_indexado" },
{ "block": "visitsByPosition", "reason": "no_indexado" } ] }
The difference matters: a field that exists and says null would mean "the portal says there is
nothing". An absent field with its reason means "we could not find out", which is what happened.
reason | What happened | What to do |
|---|---|---|
no_indexado | the listing was published very recently and the portal has not indexed it yet | wait and ask again. It is not an error |
segmento_sin_datos_suficientes | that property's market is too small for the portal to have statistics | nothing: there are no honest numbers to give |
segmento_incompleto | the property's location is missing | complete the zone and publish again |
| a portal code | the portal rejected that read | read it: it is the same kind of code as on publishing |
current and the PUT keep working even when everything else is missing. The statistics and the
simulation come from a part of the portal that may be unavailable; publishing, checking your position
and assigning do not depend on it.
If the segment had to be widened, we tell you
When your property's segment has few listings, the portal compares against a broader one. When that happens, we declare it:
{ "segment": { "propertyType": 1, "operation": 1, "locality": 2102,
"neighborhood": 20, "bedrooms": null },
"segmentWidened": ["bedrooms"] }
Label the figure with the segment that came back, not the one you asked for. A curve for "Palermo, any number of bedrooms" is not the same as one for "Palermo, 2 bedrooms".
The PUT: assigning
{ "spend": 50 }
{ "portal": "argenpropapi", "mechanism": "points",
"paId": "01M5375T7YS6PC223DWC0YQPGS",
"spend": 50, "position": 185, "calls": 3 }
position is the REAL position we read, not the one the simulation promised: we query it after
assigning.
But the portal takes a while to recompute it. Measured: assigning 5 points, the simulation said
position 463 and the immediate read still returned the previous one. It is not an error and it does
not mean it was not applied — the points were already deducted from the balance. If you want to
show the effect, ask for the GET again a while later; if you want to show it right away, use the
simulation's number and say it is an estimate.
spend: 0 is valid — it means "stop paying". That is why there is no DELETE here: taking the
publication down is a different thing.
If the portal has not reindexed yet, position does not come back and we tell you:
{ "spend": 50, "positionUnavailable": "el portal todavía no reindexó el aviso" }
We do not validate your spend against the max we read. That number comes from a read that may
be stale, and rejecting you over it would be our copy deciding instead of the portal. The portal is
the authority on what it accepts: if it rejects it, we hand you its reason.
Errors
| Code | What happened |
|---|---|
400 | A malformed parameter. It is validated before calling the portal, so it costs you no quota |
409 portal_not_connected | That account is not connected to the portal |
409 no_publicado | The property is not published there: there is no position to check |
404 | That property is not in your inventory |
501 | That portal has no paid positioning enabled here |
502 portal_rechazo | The portal rejected the assignment. reason carries its code |
Related
- Publish to a portal — this is a later step
- Check the state on the portal — "is it published?", which is a different question
- The property's states — reserved, sold, rented
- Your client's quota — how it is spent