AliBord API v1
Public REST API to integrate your business with AliBord. Create listings, get analytics, manage your catalog.
Introduction
AliBord API is a REST interface. Base URL: https://www.alibord.com
All requests and responses use JSON in UTF-8 encoding.
All dates are in the Europe/Kyiv timezone, ISO 8601 format.
How to get an API key
- Sign in to your AliBord account.
- Open «Settings» → «API & integrations».
- Click «Create key», pick the required scopes and add a whitelist.
- Copy the key immediately — it is shown only once, after the dialog closes it is hidden.
Authentication
Every request must include an Authorization header with a Bearer token:
Authorization: Bearer sk_live_abcdef1234567890abcdef1234567890abcdef1234567890
Whitelist (IP / domains)
For security, every API key must have a whitelist. API access is only available from listed IPs and domains.
| Field | Role | Example |
|---|---|---|
allowed_ips | IP whitelist — required, checked server-side | 192.0.2.10, 203.0.113.5 |
allowed_domains | Allowed domains — used for CORS (browser requests) | my-shop.com, api.my-shop.com |
Origin / Referer headers are trivial to forge from curl/server, so the domain is not a lock — only a CORS hint for browser requests.Server-side: if the client IP is not in the whitelist — 403 ip_not_whitelisted is returned immediately. If the whitelist is empty — 403 whitelist_incomplete.
For browsers: add your domain (e.g. my-shop.com) to the whitelist — otherwise the browser will block the response due to CORS.
Scopes (permissions)
Scopes are «permissions» that limit what the key can do. Grant only what is needed — principle of least privilege.
| Scope | Allows |
|---|---|
listings:read | Read your listings, categories, attributes, cities |
listings:write | Create / edit listings, upload media |
If the key is missing a scope — the API returns 403 scope_required with the required scope name in hint.
Rate limits
Depending on the account type, keys have different per-minute and per-day limits.
| Limit | Personal account | Business account |
|---|---|---|
| Requests per minute | 60 | 600 |
| Requests per day | 5 000 | 100 000 |
On exceeding — 429 rate_limit_exceeded or 429 daily_limit_exceeded. Current counters are visible via /api/v1/ping.
HTTP methods
The API uses standard HTTP methods.
| Method | Purpose | Body | Idempotent |
|---|---|---|---|
GET | Fetch data (read-only) | — | yes |
POST | Create entity / trigger action | JSON | no |
PUT / PATCH | Full / partial update | JSON | yes |
DELETE | Deletion | — | yes |
Request headers
| Header | Required | Example |
|---|---|---|
Authorization | yes | Bearer sk_live_... |
Accept | recommended | application/json |
Content-Type | for POST/PUT/PATCH | application/json; charset=utf-8 |
Origin / Referer | automatic (browser) | used for whitelist checks |
User-Agent | optional | for diagnostic logs |
Request body
For POST/PUT/PATCH the body is JSON with a Content-Type: application/json header.
For GET, parameters are passed via the query string.
Example:
POST /api/v1/listings
Authorization: Bearer sk_live_...
Content-Type: application/json
{
"title": "iPhone 17 Pro Max 256Gb",
"price": 55000,
"currency": "UAH",
"condition": "new",
"city_ref": "db5c88d0-391c-11dd-90d9-001a92567626",
"photos": ["https://cdn.example.com/1.jpg", "https://cdn.example.com/2.jpg"]
}
Response format
All responses are JSON with an ok field. true — success, false — error.
Success
{
"ok": true,
...
}
Error
{
"ok": false,
"error": "error_code",
"hint": "...",
...
}
Collections use a count field and an array named after the entity:
{
"ok": true,
"count": 2,
"listings": [ { ... }, { ... } ]
}
HTTP status codes
| Code | Meaning |
|---|---|
200 OK | Successful request |
201 Created | Resource created |
204 No Content | Success, no response body |
400 Bad Request | Malformed request (invalid JSON, missing fields) |
401 Unauthorized | Invalid / missing token |
402 Payment Required | Insufficient balance (for paid actions) |
403 Forbidden | Access denied (whitelist / scope / key status) |
404 Not Found | Resource not found |
405 Method Not Allowed | Method not allowed for this endpoint |
413 Payload Too Large | Request body exceeds size limit |
415 Unsupported Media Type | Invalid Content-Type |
422 Unprocessable Entity | Data passed schema validation but failed business rules |
429 Too Many Requests | Rate limit exceeded |
500 Internal Server Error | Internal server error |
Error codes
On error, the body contains an error field with a machine code and hint with a human-readable explanation.
{ "ok": false, "error": "error_code", ... }
| HTTP | Code | Description |
|---|---|---|
| 401 | missing_bearer_token | The Authorization header is missing or does not contain a Bearer token |
| 401 | invalid_key | Key not found, deactivated or malformed |
| 403 | key_suspended / key_revoked | Key is temporarily suspended or revoked by its owner |
| 403 | whitelist_incomplete | The key has no whitelist set — access denied |
| 403 | ip_not_whitelisted | The request IP is not in the key whitelist |
| 403 | scope_required | The key is missing the required scope — see hint |
| 429 | rate_limit_exceeded | Per-minute request limit exceeded |
| 429 | daily_limit_exceeded | Daily request limit exceeded |
GET /api/v1/ping
Verify the key and get a quick overview of limits / scopes / client IP.
Response
{
"ok": true,
"message": "pong",
"key_id": 1,
"key_prefix": "sk_live_abcdef12",
"user_id": 36,
"account_type": "personal",
"scopes": ["listings:read", "listings:write"],
"scopes_granted": ["listings:read", "listings:write"],
"rate_limit_per_minute": 60,
"daily_limit": 5000,
"total_requests": 42,
"server_time": "2026-04-20T12:34:56+03:00",
"your_ip": "203.0.113.5"
}
GET /api/v1/my/listings
List of your listings, cursor-paginated. Scope: listings:read.
Query parameters
| Parameter | Default | Description |
|---|---|---|
lang | uk | Response language: uk, ru, en |
status | active | active, sold, archived, inactive, all |
limit | 20 | Page size, 1–100 |
cursor | — | ID of the last listing from the previous page |
fields | all fields | Comma-separated list of fields — to fetch only what you need (saves bandwidth) |
Response
{
"ok": true,
"lang": "uk",
"count": 2,
"next_cursor": 118,
"listings": [
{
"id": 123,
"slug": "iphone-17-pro-max-256gb",
"title": "iPhone 17 Pro Max 256Gb",
"price": 55000,
"currency": "UAH",
"condition": "new",
"city": "Київ",
"status": "active",
"views": 248,
"vip": true,
"top": false,
"vip_until": "2026-05-20 00:00:00",
"top_until": null,
"photo": "https://cdn.alibord.com/listings/3/abc.webp",
"photos": [
"https://cdn.alibord.com/listings/3/abc.webp",
"https://cdn.alibord.com/listings/3/def.webp"
],
"url": "https://alibord.com/uk/board/iphone-17-pro-max-256gb",
"created_at": "2026-04-15 10:22:01"
}
]
}
Pass the next_cursor value into cursor on the next request. If the page is not full — next_cursor is absent.
Fields
| Field | Type | Description |
|---|---|---|
id | int | Listing ID |
slug | string | SEO slug |
title | string | Title in the selected language |
price | number | Price, may be decimal |
currency | string | UAH, USD, EUR |
condition | string \| null | new, used |
city | string \| null | City name in the selected language |
status | string | active, sold, archived, inactive |
views | int | View count |
vip, top | bool | Whether VIP / TOP promotion is active |
vip_until, top_until | datetime \| null | Date until the promotion is active |
photo | string \| null | Main photo (first from the array) |
photos | array | All listing photos |
url | string | Direct link to the listing page |
created_at | datetime | Creation date |
fields and limit — response will be smaller and limits will last longer.GET /api/v1/my/listings/{id}
Full data for a single listing, including description, attributes, contacts and moderation status. Scope: listings:read.
Response
{
"ok": true,
"lang": "uk",
"listing": {
"id": 123,
"slug": "iphone-17-pro-max-256gb",
"title": "iPhone 17 Pro Max 256Gb",
"description": "...",
"price": 55000,
"currency": "UAH",
"condition": "new",
"city": "Київ",
"city_ref": "e71f8bb8-4b33-11e4-ab6d-005056801329",
"category_id": 42,
"status": "pending",
...
},
"moderation": {
"decision": "pending",
"reason": null,
"reviewed_at": null
}
}
The moderation block is returned only for listings in pending or blocked state. reason contains the block reason if the moderator left one.
POST /api/v1/my/listings
Create a new listing. Scope: listings:write.
pending, publication happens after moderator approval.Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
title | string | yes | Title, 5–120 chars |
description | string | yes | Description, 20–5000 chars |
price | number | yes* | Price. Optional if price_type = exchange / free |
price_type | string | no | sell, exchange, free |
currency | string | no | UAH, USD, EUR |
category_id | int | yes | Category ID (see /categories) |
condition | string | no | new / used |
city_ref | string | yes* | City UUID from /cities/search |
photos | array<string> | no | Photo URLs (up to 12). Obtain via /upload/photo |
photos_wm | array<string> | no | Watermarked photo URLs, same order as photos |
photos_3d | array<string> | no | URLs of 3D tour frames (see 3D widget) |
video_url / video_thumbnail | string | no | Video URL and its thumbnail (see /upload/video) |
attributes | object | no | Object with category attributes (see /categories/{id}/attributes) |
contact_name / contact_phone | string | no | Name and phone (if omitted — taken from the profile) |
hide_phone | bool | no | Hide phone from the listing |
Response
HTTP/1.1 201 Created
{
"ok": true,
"listing_id": 2468,
"slug": "iphone-17-pro-max-256gb",
"status": "active",
"url": "https://alibord.com/uk/board/iphone-17-pro-max-256gb"
}
422 duplicate.PUT PATCH /api/v1/my/listings/{id}
Partial listing edit. Send only the fields you want to update — the rest stay as they were. Scope: listings:write.
Body (JSON, all fields optional)
{
"title": "iPhone 17 Pro Max 256Gb",
"description": "...",
"price": 52000,
"currency": "UAH",
"condition": "new",
"category_id": 42,
"city_ref": "e71f8bb8-...",
"contact_name": "...",
"contact_phone": "+380...",
"hide_phone": false,
"delivery": { ... },
"photos": ["https://...", "..."],
"photos_wm": ["https://...", "..."],
"photos_3d": ["https://...", "..."],
"video_url": "https://...",
"video_thumbnail": "https://...",
"attributes": { "brand": "apple", "memory_gb": 256 }
}
photos, photos_3d or video_url is a full replacement of that set. To keep previous media — omit the field entirely.active before the edit, it is automatically moved back to pending and re-checked — same as on the website.{ "ok": true, "listing_id": 2468, "status": "pending" }
POST /api/v1/my/listings/{id}/status
Change a listing status — activate or deactivate. Scope: listings:write. «Sold» and «delete» actions are intentionally unavailable via API — perform them from the website or admin panel.
Body (JSON)
{ "status": "inactive" }
Allowed values
| Value | Effect |
|---|---|
active | Bring back to public listing (allowed only from inactive/sold) |
inactive | Deactivate — listing is hidden from the catalog |
active is only allowed from the inactive state. From pending / blocked / sold you cannot activate via API — only a moderator/admin on the site can.GET /api/v1/categories
Category tree for creating listings. Names are in the selected language (?lang=uk|ru|en). Scope: listings:read.
{
"ok": true,
"lang": "uk",
"categories": [
{
"id": 1,
"slug": "electronics",
"name": "Електроніка",
"children": [
{ "id": 11, "slug": "phones", "name": "Телефони", "children": [...] }
]
}
]
}
GET /api/v1/categories/{id}/attributes
Attributes for a specific category — needed in the attributes field when creating a listing. Scope: listings:read.
{
"ok": true,
"category_id": 11,
"attributes": [
{
"code": "brand",
"name": "Виробник",
"type": "select",
"required": true,
"options": [
{ "value": "apple", "label": "Apple" },
{ "value": "samsung", "label": "Samsung" }
]
},
{ "code": "memory_gb", "name": "Пам'ять, ГБ", "type": "number" }
]
}
Types: text, number, select, boolean, range. For select, pass the value from the options array.
GET /api/v1/cities/search
City search for the city_ref field. Returns a Nova Poshta ref. Scope: listings:read.
Query parameters
| Parameter | Default | Description |
|---|---|---|
q | — | Search string (min 2 chars) |
lang | uk | Name language: uk, ru, en |
limit | 10 | Max results (1–50) |
{
"ok": true,
"cities": [
{
"ref": "e71f8bb8-4b33-11e4-ab6d-005056801329",
"name": "Київ",
"type": "м.",
"region": "Київська",
"label": "м. Київ, Київська",
"warehouses": 1823
}
]
}
POST /api/v1/ai/suggest-category
AI category suggestion based on title and description. Handy for auto-filling the form. Scope: listings:write.
{
"title": "iPhone 17 Pro Max 256Gb",
"description": "...",
"lang": "uk"
}
Success
{
"ok": true,
"category_id": 117,
"category_path": "Електроніка › Телефони › Apple",
"category_slug": "apple"
}
POST /api/v1/upload/photo
Upload a photo to the CDN — get a URL for the photos field of a new listing. Scope: listings:write.
Format: multipart/form-data, file field. Limit — 12 MB. Accepts JPG, PNG, WebP, HEIC. Automatically converted to WebP and resized to 1600px.
curl -X POST "https://www.alibord.com/api/v1/upload/photo" \
-H "Authorization: Bearer sk_live_..." \
-F "file=@/path/to/image.jpg"
HTTP/1.1 201 Created
{
"ok": true,
"url": "https://pub-...r2.dev/listings/36/1776203001_123.webp",
"url_wm": "https://pub-...r2.dev/pub/36/abc123...webp",
"width": 1200,
"height": 1600
}
url — original, url_wm — watermarked. When creating the listing, pass them correspondingly into photos / photos_wm.POST /api/v1/upload/video
Upload a short video (up to 60s) for a listing. Scope: listings:write.
- Formats: MP4, MOV, WebM (H.264 or H.265)
- Max size: 50 MB, max duration: 60s
- One video per listing
HTTP/1.1 201 Created
{
"ok": true,
"url": "https://pub-...r2.dev/listings/36/1776213111_456.mp4",
"thumbnail": "https://pub-...r2.dev/listings/36/1776213111_456_thumb.webp"
}
3D widget
Ready-made widget to capture a 3D tour of a product using the device camera. Embed with one line, it uploads photos via your server-side proxy (so the API key stays secret).
Embedding
<script src="https://www.alibord.com/widget/capture3d.js"></script>
<button onclick="startCapture()">3D</button>
<script>
function startCapture() {
AliBord.capture3d({
uploadEndpoint: '/my-proxy/upload-photo',
lang: 'uk',
onComplete: (urls) => console.log(urls.length),
onCancel: () => {},
onError: (err) => console.error(err),
});
}
</script>
How it works
- User clicks the «3D» button
- The widget requests camera access and opens fullscreen
- A series of frames is captured (automatically or on click)
- Frames are sent via POST to your
uploadEndpoint - Your server forwards them to
/api/v1/upload/photowith the API key - The
onComplete(urls)callback receives the array of URLs — pass it in thephotos_3dfield when creating the listing
/api/v1/upload/photo directly from the browser — that leaks the API key. Always use a server-side proxy.Server proxy example (PHP)
<?php
$API_KEY = getenv('ALIBORD_API_KEY');
if (empty($_FILES['file'])) {
http_response_code(400);
echo json_encode(['ok'=>false, 'error'=>'no_file']); exit;
}
$ch = curl_init('https://www.alibord.com/api/v1/upload/photo');
$cfile = new CURLFile($_FILES['file']['tmp_name'], $_FILES['file']['type'], $_FILES['file']['name']);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $API_KEY],
CURLOPT_POSTFIELDS => ['file' => $cfile],
CURLOPT_TIMEOUT => 60,
]);
$resp = curl_exec($ch);
http_response_code((int)curl_getinfo($ch, CURLINFO_HTTP_CODE));
header('Content-Type: application/json; charset=utf-8');
echo $resp;
Moderation
All listings created via API go through the same moderation as listings from the website.
- Listings in safe categories are usually auto-approved —
activeright away. - The rest go into
pendingand are checked by a moderator (~15 min during business hours).
| Status | Effect |
|---|---|
pending | Awaiting moderator review |
active | Published and visible in the catalog |
blocked | Rejected by moderator — see moderation.reason |
sold | Marked as sold |
archived | Moved to archive due to expiration |
inactive | Deactivated by the owner |
To track status, poll GET /my/listings/{id} — the moderation block will carry the decision and reason (if any).
Example: cURL
# List your listings (in English)
curl -H "Authorization: Bearer sk_live_..." \
"https://www.alibord.com/api/v1/my/listings?lang=en&limit=20"
# Only the fields you need — saves bandwidth
curl -H "Authorization: Bearer sk_live_..." \
"https://www.alibord.com/api/v1/my/listings?fields=id,title,price,currency,photo&limit=50"
# Create a new listing
curl -X POST "https://www.alibord.com/api/v1/my/listings" \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"title": "iPhone 17 Pro Max 256Gb",
"description": "...",
"price": 55000,
"currency": "UAH",
"category_id": 42,
"condition": "new",
"city_ref": "e71f8bb8-4b33-11e4-ab6d-005056801329",
"photos": ["https://my-shop.com/images/iphone-1.jpg"],
"contact_phone": "+380501234567"
}'
Example: PHP
<?php
$API_BASE = 'https://www.alibord.com';
$API_KEY = 'sk_live_...';
$ch = curl_init($API_BASE . '/api/v1/my/listings?lang=en&limit=10&fields=id,title,price,currency,city,photo');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $API_KEY,
'Accept: application/json',
],
]);
$body = curl_exec($ch);
$data = json_decode($body, true);
curl_close($ch);
if (!empty($data['ok'])) {
foreach ($data['listings'] as $l) {
echo $l['title'] . ' — ' . $l['price'] . ' ' . $l['currency'] . "\n";
}
} else {
echo 'Error: ' . ($data['error'] ?? 'unknown');
}
Example: JavaScript (fetch)
// WARNING: never from a browser directly — only via your server-side proxy! The key must not reach the frontend.
const params = new URLSearchParams({
lang: 'uk', limit: '20',
fields: 'id,title,price,currency,city,photo,url'
});
const r = await fetch('https://www.alibord.com/api/v1/my/listings?' + params, {
headers: { 'Authorization': 'Bearer sk_live_...' }
});
const data = await r.json();
if (data.ok) {
data.listings.forEach(l => console.log(l.title, l.price, l.currency, l.city));
} else {
console.error(data.error);
}
allowed_domains for the key. Otherwise the browser will block the response.