Bazarok Partner API — OAuth 2.0 integration
Version 1 · Base URL: https://www.bazarok.co.uk · Ukrainian version: partner_api_oauth_uk.md
Your CRM can publish, update and remove the real-estate listings of any number of agencies on Bazarok, and handle the messages their clients send. Each agency connects its Bazarok account to your CRM itself, once, with one click (OAuth 2.0): there is nothing to set up on our side per agency.
| Agency action in your CRM | Bazarok endpoint |
|---|---|
| "Connect Bazarok" | GET /oauth_authorize.php → POST /oauth_token.php |
| Publish | POST /api/v1/listings |
| Update | PUT /api/v1/listings/{external_id} |
| Remove a listing | DELETE /api/v1/listings/{external_id} |
| New client messages | webhook, or GET /api/v1/messages?since_id=… |
| Reply | POST /api/v1/conversations/{id}/messages |
| "Disconnect Bazarok" | POST /oauth_revoke.php (or DELETE /api/v1/connection) |
What we need from you: your CRM's name, your redirect URI(s) (https), and (recommended) a webhook URL (https) for new messages. What you get from us: a client_id, a client_secret and, if you gave a webhook URL, a webhook_secret. Keep the secrets on your servers only.
1. Connecting an agency (OAuth 2.0, authorization code)
1.1 Send the agency to Bazarok
GET https://www.bazarok.co.uk/oauth_authorize.php
?response_type=code
&client_id=<client_id>
&redirect_uri=https://<your-crm>/oauth/bazarok/callback (exactly as registered)
&state=<random value, checked by your CRM on return>
[&code_challenge=<BASE64URL(SHA256(verifier))>&code_challenge_method=S256] (PKCE, optional)The agency logs in to Bazarok (or creates an account), sees what your CRM will be allowed to do, and clicks Allow or Deny. Bazarok then redirects to the redirect_uri:
- allowed:
?code=<code>&state=<state>— the code is valid 10 minutes and works once; - denied:
?error=access_denied&state=<state>.
1.2 Exchange the code (server to server)
POST https://www.bazarok.co.uk/oauth_token.php
Authorization: Basic base64(client_id:client_secret) (or client_id / client_secret in the body)
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=<code>&redirect_uri=<same redirect_uri>[&code_verifier=<verifier>]{ "access_token": "…", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "…", "account_id": 123456 }account_id is the agency's Bazarok account id: store it with the agency's tokens. Each agency has its own tokens.
1.3 Refresh
The access token lasts 1 hour, the refresh token 1 year. Refresh tokens rotate: each refresh returns a new pair and invalidates the previous refresh token — always store the new one.
POST /oauth_token.php grant_type=refresh_token&refresh_token=<refresh_token> (client authentication as above)Refresh when you get 401 token_expired, or a few minutes before expires_in.
1.4 Disconnect
POST /oauth_revoke.php token=<access or refresh token> (client authentication as above)Revokes the whole connection between this agency and your CRM (all its tokens). Reconnecting requires the agency to go through 1.1 again. Same effect: DELETE /api/v1/connection with the access token.
OAuth errors follow RFC 6749: {"error": "invalid_grant", "error_description": "…"}.
2. API conventions
- Every call:
Authorization: Bearer <access_token of the agency>; JSON bodies withContent-Type: application/json, UTF-8. Dates:YYYY-MM-DD HH:MM:SS, server time (Europe/Paris). - Errors: HTTP status + a stable machine-readable code (test the code, not the message):
{ "error": { "code": "validation_error", "message": "Some fields are missing or invalid.",
"fields": { "price": "required, number > 0", "contact.phone": "invalid phone number: 12" } } }| HTTP | error.code | Meaning / what to do |
|---|---|---|
| 401 | invalid_token | Token unknown or revoked: ask the agency to connect Bazarok again. |
| 401 | token_expired | Refresh the token and retry. |
| 422 | validation_error | Missing/invalid fields — fields lists them (show them next to the fields). |
| 422 | listing_rejected | The listing could not be saved (usually the location). |
| 403 | account_expired | The agency's trial/subscription has ended: a Bazarok subscription is needed. |
| 403 | quota_exceeded | Active listings limit of the agency reached. |
| 409 | already_exists | POST with an external_id already published: use PUT. |
| 404 | not_found | Unknown listing or conversation. |
| 403 | blocked | The client blocked the agency: the reply cannot be delivered. |
| 429 | rate_limited | More than 120 requests in a minute for this agency: wait Retry-After seconds. |
2.1 Working with hundreds of agencies
- Messages: use the webhook (6.1 A) rather than polling every agency every minute.
- Throttle your calls: at most 5 requests in parallel for your whole CRM, and one at a time per agency. Publishing downloads the photos during the call (up to ~60 s), so a first import of an agency with hundreds of listings takes time: run it as a background queue.
- Retry
5xxanswers and timeouts later with an increasing delay (1 min, 5 min, 30 min…). Do not retry4xxanswers unchanged. - Only send changes:
PUTa listing when it changed in your CRM, not on every sync.
Limit: 120 requests per minute per agency (headers X-RateLimit-Limit and X-RateLimit-Remaining on every answer). Above it: 429 rate_limited with a Retry-After header (seconds).
3. Account — GET /api/v1/me
{ "account_id": 123456, "name": "ESTATYQ", "email": "…", "phone": "…",
"status": "trial", // trial | active | expired
"trial_ends_at": "2026-12-07 10:00:00",
"subscription": null, // or { "status": "active", "paid_until": "…" }
"listings": { "active": 212, "allowed": 300 },
"site": { "domain": "bazarok.co.uk", "country_code": "ua",
"currencies": ["uah", "usd", "eur"], // accepted by "currency" (the first one is the default)
"land_units": ["сотка", "га"] } } // accepted by "land_area_unit" (the first one is the default)The API works the same way on every site of the group (bazarok.ua, bazarok.pl, bazarok.fr, bazarok.co.uk, buenanuncios.es, bonsanuncios.pt, skelbus.lt): call it on the site where the agency publishes. site gives the currencies and land-area units that site accepts; listing titles are generated in the site's language.
4. Locations
A listing is located with a Bazarok city_id (recommended), or with city + region names (Ukrainian or Russian, exactly as in Bazarok).
GET /api/v1/locations/cities?q=Київ→{ "cities": [ { "city_id": 1, "name": "Київ", "name_ru": "Киев", "region": "Київська область", "region_ru": "…" } ] }GET /api/v1/locations/districts?city_id=1→{ "districts": [ { "district_id": 7, "name": "Печерський", "name_ru": "Печерский" } ] }
Map your own cities to city_id once and store the mapping (it is the same for all agencies).
5. Listings
external_id = the property id in your CRM (1–60 characters A-Z a-z 0-9 . _ -), unique per agency. It identifies the listing in all later calls; Bazarok's own id is returned for information.
Publish — POST /api/v1/listings → 201
{
"external_id": "CRM-48213",
"deal_type": "rent", // sale | rent
"property_type": "apartment", // apartment | room | house | land | commercial | garage
"price": 18000, "currency": "uah", // one of site.currencies (GET /me) — monthly price for rent
"description": "Світла 2-кімнатна квартира…", // 10–10000 characters
"rooms": 2, "area": 54.5, "living_area": 30, // m²
"land_area": null, "land_area_unit": "сотка", // unit: one of site.land_units (GET /me)
"floor": 5, "floors_total": 9,
"lift": true, "balcony": true, "new_building": false, "parking": false, "furnished": true,
"city_id": 1, "district": "Печерський", "address": "вул. Лесі Українки, 10",
"lat": 50.4301, "lon": 30.5402,
"photos": [ "https://cdn.example.com/1.jpg", "https://cdn.example.com/2.jpg" ], // up to 15, public http(s) URLs, first = cover
"contact": {
"name": "John",
"phone": "0671234567,0501234567"
}
}Required: external_id, deal_type, property_type, price, description, city_id (or city + region). The title is generated by Bazarok from the type, rooms, area and location.
contact (optional): the realtor of the agency in charge of this listing.
name: up to 100 characters.phone: required insidecontact; 1 to 5 numbers, comma-separated ("0671234567,0501234567") or a JSON array (["+380671234567", "0501234567"]); digits, spaces,+ ( ) -, 9 to 15 digits each.- Visitors who click "Show phone number" on the listing get the first number, with the contact's name; the other numbers are kept and returned by the API.
- Without
contact, the phone of the agency account is shown.
Response:
{ "listing": { "id": 9876543, "external_id": "CRM-48213", "status": "active",
"url": "https://www.bazarok.co.uk/oholoshennya/…-9876543.html",
"title": "Квартира 2-кімн. оренда Київ, Печерський – 54.5 м²", "photos_count": 2,
"contact": { "name": "John", "phone": "0671234567,0501234567" },
"updated_at": "2026-10-27 14:02:11" } }status: active (online) · expired · inactive · deleted.
Update — PUT /api/v1/listings/{external_id} → 200
Same body as publish: full replacement of price, text, photos, characteristics, location and contact (external_id may be omitted). A field you leave out is cleared — in particular, send contact again or the agency's phone will be shown. Photos are downloaded again only if the URL list changed.
Status — GET /api/v1/listings/{external_id} · Remove — DELETE /api/v1/listings/{external_id}
A removed listing can be published again with POST and the same external_id.
6. Messages
A conversation = one client about one listing (or about the agency page). Its id is "<listing id>-<client id>" ("0-<client id>" for the agency page). Messages are plain text.
{ "id": 551234, "conversation_id": "9876543-777", "direction": "incoming", // incoming (client) | outgoing (agency)
"text": "Добрий день, квартира ще вільна?", "is_auto": false, "is_read": false,
"created_at": "2026-10-27 14:10:03" }is_auto: automatic questions sent by Bazarok on behalf of the agency (phone, visit, budget…).
6.1 Getting new messages
A. Webhook (recommended). For every message received by a connected agency, Bazarok POSTs to your webhook URL (within about a minute):
POST <webhook_url>
Content-Type: application/json
X-Bazarok-Signature: sha256=<hex HMAC-SHA256 of the raw body with the webhook_secret>
{ "event": "message.created", "account_id": 123456, "message": { …message object… } }Check the signature, find the agency by account_id, and answer 2xx quickly (process the message afterwards). A non-2xx answer is retried about every minute, in order; while your URL fails, the messages of all your agencies wait. The same message may occasionally be delivered twice: deduplicate on message.id.
B. Polling. GET /api/v1/messages?since_id=<last id received>&limit=100 with the agency's token → { "messages": [ … ], "next_since_id": 551240, "has_more": false } — all messages of the agency's conversations (incoming and outgoing) with id > since_id, oldest first. Store next_since_id per agency. Also useful to catch up after an outage of your webhook endpoint.
6.2 Conversations
GET /api/v1/conversations?limit=50[&before_id=<last_message.id of the last item>]— most recent first:
{ "conversations": [ {
"id": "9876543-777", "unread_count": 2,
"client": { "id": 777, "name": "Олена", "phone": "+380671234567" }, // phone if the client left it, else null
"listing": { "id": 9876543, "external_id": "CRM-48213", "title": "…", "url": "…" }, // null for the agency page
"last_message": { … } } ],
"has_more": true }If the agency's trial has ended without a subscription, the phone of clients received since then is hidden: "phone": null, "phone_hidden": true.
GET /api/v1/conversations/{id}/messages[?since_id=…]— conversation details + its messages, oldest first.POST /api/v1/conversations/{id}/read— mark the client's messages as read.
6.3 Reply — POST /api/v1/conversations/{id}/messages → 201
{ "text": "Так, вільна. Коли вам зручно подивитися?" } // 1–2000 charactersThe reply is sent in the Bazarok chat in the agency's name: the client sees it instantly if online, otherwise gets a push/email notification. Only existing conversations can be answered (the client must have written first).
7. Getting started
- Send us your CRM name, redirect URI(s) and webhook URL; we send back
client_id,client_secretandwebhook_secret. - Add a "Connect Bazarok" button for agencies (1.1), exchange the code (1.2), store the tokens with
account_id. - Map your cities to
city_id(GET /api/v1/locations/cities). - With a test agency: publish a listing, open the returned
url, update it, remove it. - Send a message to the test listing from another Bazarok account and check your webhook receives it.
Questions: contact@bazarok.co.uk