Your client's plans on the portal
GET /property-v1/customers/{portal}/account
When you publish, you send a plan. To choose it you need to know which plans your client has and
how many publications are still free — and that data lives on the portal, not in our system.
You have it in two places, and it is worth using both:
| Where | What it gives you | Cost |
|---|---|---|
GET /property-v1/customers | The last thing we read, with its timestamp | nothing |
GET /property-v1/customers/{portal}/account | What the portal says right now, and it refreshes the above | 1 call from your client's allowance |
The cheap route is to request the client — which is free — and look at the plansStale field. If it
says true, there was a publication or a takedown after that read; that is when you decide whether to
spend the call.
How to request it
curl https://property-api.mapaprop.com/property-v1/customers/zonapropapi/account \
-H "Authorization: Bearer $TOKEN" \
-H "x-client-ref: cliente-42"
200 response:
{
"portal": "zonapropapi",
"portalAccountId": "17064147",
"plans": [
{ "plan": "DESARROLLOS_HOME", "planTotal": 5, "planAvailable": 1 },
{ "plan": "HOME", "planTotal": 80, "planAvailable": 31 },
{ "plan": "SIMPLE", "planTotal": 80, "planAvailable": 20 }
],
"plansReadAt": "2026-10-07T19:32:22.975Z",
"plansStale": false,
"planExpirations": [
{ "plan": "HOME", "date": "2026-12-31T03:00:00.000Z", "quantity": 85 }
],
"cached": true
}
| Field | What it is |
|---|---|
plans[].plan | The plan name, exactly as the portal calls it. It is the value you send as plan when publishing |
plans[].planTotal | How many are contracted. It can come back null if the portal does not report it |
plans[].planAvailable | How many publications are still free |
plansReadAt | When we read this from the portal |
plansStale | Always false here: we have just read it |
planExpirations | When they expire and how many. Omitted if the portal reports none |
cached | Whether we stored the copy. When false, the free read will keep giving you the previous one |
The portal only lists the plans that still have room. A plan your client contracted but already
used up does not appear in the list. So "it is not in plans" does not mean "they do not have it
contracted": it can mean either, and the portal does not tell them apart.
That is what makes the plan publication error confusing: it says "the company has used up all of its available ones" and reads as if the account had nothing at all.
Where it fits in the flow
1. POST /property-v1/customers you declare the client
2. you connect their portal account ← the plans come in here, at no extra cost
3. GET /property-v1/customers you see them, free, with their timestamp
4. GET /property-v1/customers/{portal}/account you refresh them against the portal (1 call)
5. PUT …/publication/{portal} you publish
Step 2 already stores the plans: the call that verifies your client authorised us is the same one that brings them, so by the time you connect you already have them at no cost.
After every publication or takedown, step 3 returns them with plansStale: true.
Why we do not refresh them on our own
Because every read comes out of your client's monthly allowance on the portal —which each portal defines its own way; on Zonaprop it is 1,500 calls— and it is the same one you use to publish and to fetch enquiries. Refreshing on every publication would double your integration's spend, and the decision about when it is worth it is yours.
And there is a stronger reason: the portal does not tell us which plan a publication consumed. Its response carries the listing code, its id and its errors — no plan. If we subtracted the one you requested we could be subtracting the wrong one, so we would rather tell you the copy went stale than give you a made-up number.
If you never call it
Nothing serious happens: you send your plan when publishing and, if your client does not have it, the
publish response tells you which ones they do have in the planIssue block. This endpoint exists so
that you can look it up whenever you want, not so that you have to.
Response codes
| Code | error | What happened |
|---|---|---|
200 | — | The plans, freshly read from the portal |
400 | portal is required | The portal is missing from the path |
400 | clientRef is required (header x-client-ref) | The client is not identified |
401 | Unauthorized | Token missing or invalid |
403 | Missing scope: property-api-read | Your token cannot read |
403 | Missing required scope: zonapropapi:connect | Your token is not enabled for that portal |
409 | portal_not_connected | Your client has no active connection with that portal |
409 | connection_incomplete | The connection exists but is incomplete. Connect it again |
501 | portal_sin_consulta_de_cuenta | We do not look up the account on that portal yet |
502 | portal_no_contesto | The portal did not answer. Retry; we do not overwrite the previous copy |
The 502 does not erase what we already had. Storing an empty list because the portal did not
answer would say your client has no plans, and that would be false. The previous copy is still
available in the client GET with its timestamp.
Portals
| Portal | {portal} | Status |
|---|---|---|
| Zonaprop | zonapropapi | available |
| Argenprop · Cabaprop · MercadoLibre | — | 501 |
A 501 does not mean you got it wrong: it means that portal does not have this lookup on our side yet.
Related
- Client
GET— the free read, withplansStale - Publishing — where the
planis sent and whatplanIssuesays - Monthly allowance and limits — the 1,500 calls