KupiProdaj API
REST API for managing listings programmatically. Upload images, create, update, and delete listings.
Authentication
All API requests require a Bearer token in the Authorization header. Create API keys in your dashboard.
curl -H "Authorization: Bearer kp_live_abc123..." \
https://kupiprodaj.me/api/v1/listingsX-RateLimit-Remaining and X-RateLimit-Reset are included in every response.{
"error": "Invalid or missing API key"
}{
"error": "Rate limit exceeded",
"retryAfter": 42
}Upload images to get URLs, then pass those URLs when creating or updating a listing. This is a two-step process: upload first, then create the listing with the returned URLs.
Request (multipart/form-data)
| Parameter | Type | Required | Description |
|---|---|---|---|
| files | File[] | required | Image files. Max 10 per request, max 5MB each. Allowed: JPEG, PNG, WebP, GIF. |
| listingSlug | string | optional | Optional slug for organizing files. Defaults to 'temp'. |
Example: upload with curl
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "[email protected]" \
-F "[email protected]" \
https://kupiprodaj.me/api/v1/upload{
"urls": [
"https://images.kupiprodaj.me/listings/temp/1710000000-abc123.jpg",
"https://images.kupiprodaj.me/listings/temp/1710000000-def456.jpg"
]
}Example: upload with JavaScript
const form = new FormData();
form.append("files", photoFile1);
form.append("files", photoFile2);
const res = await fetch("https://kupiprodaj.me/api/v1/upload", {
method: "POST",
headers: { "Authorization": "Bearer YOUR_API_KEY" },
body: form, // Do NOT set Content-Type — browser sets it with boundary
});
const { urls } = await res.json();
// Use urls in the create listing requestExample: upload with Python
import requests
files = [
("files", ("photo1.jpg", open("photo1.jpg", "rb"), "image/jpeg")),
("files", ("photo2.jpg", open("photo2.jpg", "rb"), "image/jpeg")),
]
res = requests.post(
"https://kupiprodaj.me/api/v1/upload",
headers={"Authorization": "Bearer YOUR_API_KEY"},
files=files,
)
urls = res.json()["urls"]Returns a paginated list of your own listings.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| page | integer | optional | Page number (default: 1) |
| limit | integer | optional | Items per page, 1-100 (default: 20) |
| status | string | optional | Filter by status: DRAFT, PENDING_MODERATION, ACTIVE, REJECTED, INACTIVE, SOLD, ARCHIVED, EXPIRED |
Example
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://kupiprodaj.me/api/v1/listings?page=1&limit=10&status=ACTIVE"{
"listings": [
{
"id": "clx...",
"title": "iPhone 15 Pro 256GB",
"slug": "iphone-15-pro-256gb-a1b2c3",
"description": "Kao nov, korišćen 3 mjeseca...",
"price": 899,
"currency": "EUR",
"status": "ACTIVE",
"externalRef": null,
"category": { "id": "clx...", "name": "Mobilni telefoni", "slug": "elektronika/telefoni/mobilni-telefoni" },
"city": { "id": "clx...", "name": "Podgorica", "slug": "podgorica" },
"country": { "id": "clx...", "name": "Crna Gora" },
"images": [
{ "id": "clx...", "url": "https://images.kupiprodaj.me/listings/...", "alt": "iPhone 15 Pro", "order": 0 }
],
"attributes": { "brand": "Apple", "model": "iPhone 15 Pro", "storage": "256GB" },
"featured": false,
"views": 42,
"createdAt": "2026-03-01T12:00:00.000Z",
"updatedAt": "2026-03-01T12:00:00.000Z"
}
],
"total": 1,
"page": 1,
"limit": 10,
"pages": 1
}Create a new listing. Upload images first via /api/v1/upload, then pass the returned URLs here.
Request body (JSON)
| Parameter | Type | Required | Description |
|---|---|---|---|
| title | string | required | Listing title (min 3 characters) |
| categoryId | string | required | Category UUID. See reference table below. |
| description | string | optional | Listing description |
| price | number | optional | Price (null for 'negotiable') |
| currency | string | optional | Currency code (default: EUR) |
| cityId | string | optional | City UUID. Country auto-resolved from city. |
| countryId | string | optional | Country UUID (optional if cityId provided) |
| attributes | object | optional | Category-specific attributes (brand, model, year, etc.) |
| imageUrls | string[] | optional | Array of image URLs from /api/v1/upload |
| externalRef | string | optional | Your external reference ID for tracking |
Full example: upload + create
# Step 1: Upload images
IMAGE_URLS=$(curl -s -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "[email protected]" \
-F "[email protected]" \
https://kupiprodaj.me/api/v1/upload | jq -r '.urls')
# Step 2: Create listing with image URLs
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "iPhone 15 Pro 256GB",
"description": "Kao nov, korišćen 3 mjeseca. Komplet oprema.",
"price": 899,
"currency": "EUR",
"categoryId": "CATEGORY_UUID_HERE",
"cityId": "CITY_UUID_HERE",
"imageUrls": '"$IMAGE_URLS"',
"attributes": {
"brand": "Apple",
"model": "iPhone 15 Pro",
"storage": "256GB",
"color": "Crna",
"condition": "Kao novo"
},
"externalRef": "my-shop-sku-12345"
}' \
https://kupiprodaj.me/api/v1/listings{
"id": "clx...",
"slug": "iphone-15-pro-256gb-a1b2c3",
"title": "iPhone 15 Pro 256GB",
"status": "ACTIVE",
"category": { "id": "clx...", "name": "Mobilni telefoni", "slug": "elektronika/telefoni/mobilni-telefoni" },
"images": [
{ "id": "clx...", "url": "https://images.kupiprodaj.me/listings/...", "alt": "iPhone 15 Pro 256GB", "order": 0 },
{ "id": "clx...", "url": "https://images.kupiprodaj.me/listings/...", "alt": "iPhone 15 Pro 256GB", "order": 1 }
],
"createdAt": "2026-03-10T12:00:00.000Z"
}{
"error": "Title is required (min 3 chars)"
}Get full details of one of your listings by its ID.
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://kupiprodaj.me/api/v1/listings/LISTING_ID{
"id": "clx...",
"title": "iPhone 15 Pro 256GB",
"slug": "iphone-15-pro-256gb-a1b2c3",
"description": "Kao nov, korišćen 3 mjeseca...",
"price": 899,
"currency": "EUR",
"isPriceNegotiable": false,
"status": "ACTIVE",
"externalRef": "my-shop-sku-12345",
"category": { "id": "clx...", "name": "Mobilni telefoni", "slug": "elektronika/telefoni/mobilni-telefoni" },
"city": { "id": "clx...", "name": "Podgorica", "slug": "podgorica" },
"country": { "id": "clx...", "name": "Crna Gora" },
"images": [ ... ],
"attributes": { "brand": "Apple", "model": "iPhone 15 Pro", "storage": "256GB" },
"featured": false,
"views": 42,
"createdAt": "2026-03-01T12:00:00.000Z",
"updatedAt": "2026-03-01T12:00:00.000Z"
}Update any fields of your listing. Only send the fields you want to change. To replace images, upload new ones first, then pass the new imageUrls array.
Request body (JSON) — all fields optional
| Parameter | Type | Required | Description |
|---|---|---|---|
| title | string | optional | New title |
| description | string | optional | New description |
| price | number | optional | New price |
| currency | string | optional | New currency |
| isPriceNegotiable | boolean | optional | Price negotiable flag |
| status | string | optional | ACTIVE, INACTIVE, SOLD, or ARCHIVED |
| categoryId | string | optional | Move to different category |
| cityId | string | optional | Change city |
| countryId | string | optional | Change country |
| attributes | object | optional | Replace all attributes |
| imageUrls | string[] | optional | Replace all images with new URLs |
Example: update price and status
curl -X PUT \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "price": 799, "status": "ACTIVE" }' \
https://kupiprodaj.me/api/v1/listings/LISTING_IDExample: replace images
# Upload new photos
NEW_URLS=$(curl -s -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "files=@new_photo.jpg" \
https://kupiprodaj.me/api/v1/upload | jq '.urls')
# Update listing images (replaces all existing)
curl -X PUT \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "imageUrls": '"$NEW_URLS"' }' \
https://kupiprodaj.me/api/v1/listings/LISTING_ID{
"id": "clx...",
"title": "iPhone 15 Pro 256GB",
"slug": "iphone-15-pro-256gb-a1b2c3",
"status": "ACTIVE",
"images": [ ... ],
"updatedAt": "2026-03-10T14:30:00.000Z"
}Permanently deletes a listing and all associated images from storage (R2). This action cannot be undone.
curl -X DELETE \
-H "Authorization: Bearer YOUR_API_KEY" \
https://kupiprodaj.me/api/v1/listings/LISTING_ID{
"success": true,
"id": "clx..."
}{
"error": "Not found"
}Returns the effective attribute schema for a category — its own attributes plus any inherited from ancestor categories (nearest wins). This is the exact set that attributes is validated against when you create or update a listing: unknown keys, wrong types, or values outside options are rejected with a 422.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | required | Category id (see Categories Reference below) |
Example
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://kupiprodaj.me/api/v1/categories/CATEGORY_ID/attributes{
"categoryId": "clx...",
"categorySlug": "vozila/automobili",
"categoryName": "Automobili",
"attributes": [
{
"key": "brand",
"label": "Marka",
"labelEn": "Brand",
"labelRu": "Марка",
"type": "SELECT",
"options": ["Audi", "BMW", "Volkswagen", "..."],
"unit": null,
"isRequired": true,
"isFilterable": true
},
{
"key": "year",
"label": "Godište",
"labelEn": "Year",
"labelRu": "Год",
"type": "NUMBER",
"options": null,
"unit": null,
"isRequired": false,
"isFilterable": true
}
]
}{
"error": "Category not found"
}Complete Workflow Example
Here is the typical flow for creating a listing with images using JavaScript/TypeScript:
const API_KEY = "kp_live_...";
const BASE_URL = "https://kupiprodaj.me/api/v1";
async function createListingWithImages(data, imageFiles) {
// Step 1: Upload images
const form = new FormData();
for (const file of imageFiles) {
form.append("files", file);
}
const uploadRes = await fetch(`${BASE_URL}/upload`, {
method: "POST",
headers: { "Authorization": `Bearer ${API_KEY}` },
body: form,
});
const { urls } = await uploadRes.json();
// Step 2: Create listing with image URLs
const listingRes = await fetch(`${BASE_URL}/listings`, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
...data,
imageUrls: urls,
}),
});
return await listingRes.json();
}
// Usage:
const listing = await createListingWithImages(
{
title: "Samsung Galaxy S24 Ultra",
description: "Potpuno nov, zapakovan.",
price: 1199,
currency: "EUR",
categoryId: "CATEGORY_UUID",
cityId: "CITY_UUID",
attributes: {
brand: "Samsung",
model: "Galaxy S24 Ultra",
storage: "512GB",
color: "Titanium Black",
},
externalRef: "shop-sku-001",
},
[file1, file2, file3]
);
console.log("Created:", listing.id, listing.slug);Error Codes
| Status | Description |
|---|---|
| 400 | Bad request — missing/invalid parameters |
| 401 | Unauthorized — invalid or missing API key |
| 404 | Not found — listing doesn't exist or doesn't belong to you |
| 429 | Rate limit exceeded — wait and retry |
| 500 | Server error — contact support |