API v1
Подключение сайта через API
Заказ с вашего сайта одним запросом появляется на доске Gayidvebi.ge (колонка «საიტიდან»). Когда продавец меняет статус, сайт узнаёт об этом через webhook. Также можно читать товары и остатки. Документация ниже — на английском.
- Base URL
https://www.gayidvebi.ge/api/v1- Format
- JSON, UTF-8
- OpenAPI
- /api/v1/openapi.json
Quickstart
- The seller creates a key in Settings → Integrations → Custom site (API).
- Check the key — a 200 answer means it works, and Settings shows "connected":
curl https://www.gayidvebi.ge/api/v1/ping \
-H "Authorization: Bearer gyd_live_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"- After checkout, send the order:
<?php
// $orderId, $customer, $cart come from your checkout
$order = [
'id' => (string) $orderId,
'customer' => ['name' => $customer['name'], 'phone' => $customer['phone']],
'shipping' => ['city' => $customer['city'], 'address' => $customer['address'], 'fee' => 6],
'payment' => ['method' => 'cod', 'paid' => false],
'items' => array_map(fn ($line) => [
'product_id' => (string) $line['product_id'],
'name' => $line['name'],
'options' => $line['options'] ?? [], // ['ზომა' => 'L']
'quantity' => (int) $line['qty'],
'price' => (float) $line['unit_price'],
], $cart),
];
$ch = curl_init('https://www.gayidvebi.ge/api/v1/orders');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('GAYIDVEBI_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($order, JSON_UNESCAPED_UNICODE),
]);
$response = json_decode((string) curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status === 201 || $status === 200) {
// Saved (or it was already saved). $response['order']['crm_id']
} elseif ($status === 0 || !empty($response['retry'])) {
// No answer, 429 or 5xx: try again later with the same id — never saved twice
} else {
error_log('Gayidvebi refused the order: ' . json_encode($response));
}Authentication
Send the header Authorization: Bearer gyd_live_… with every request. A key belongs to one site.
- Requests from a browser (with an Origin header) are refused — 403.
- A key in the URL (?key=…) — 400.
- Limit: 120 requests a minute per key. Over it — 429 with Retry-After.
- Creating a new key stops the old one at once (401 key_revoked).
- Use www: gayidvebi.ge (without www) redirects, and some HTTP clients don't repeat a POST after a redirect.
Create an order
POST/api/v1/orders
The order lands in the "საიტიდან" column — the seller hears a sound, then confirms it. Stock is deducted automatically.
| Field | Type | Description |
|---|---|---|
| id* | string | Your order id (1–64 chars: letters, digits, . _ : -). The same id is never saved twice. |
| number | string | The order number the customer sees (defaults to id). |
| created_at | ISO 8601 | When it was placed (defaults to now). |
| customer.name | string | Full name (or customer.first_name + customer.last_name). |
| customer.phone* | string | Phone in any format: +995 555 12 34 56, 555123456… |
| customer.email | string | Added to the comment if the seller turned it on. |
| customer.personal_id | string | Personal ID number (for the waybill). |
| shipping.city / shipping.address | string | Delivery city and address. |
| shipping.fee | number | Delivery fee in GEL. |
| shipping.paid_by | "customer" | "shop" | Who pays the courier. If omitted: shop when paid online, customer for cash on delivery. |
| payment.method | string | Payment code: cod / cash (on delivery), bank_transfer, or any code of yours — the seller maps it in Settings. |
| payment.title / payment.paid | string / boolean | The method's name and whether it is paid. |
| items[]* | array | 1–200 items. |
| items[].name* | string | Product name without the variation ("T-shirt", not "T-shirt - L"). Matched to the CRM product by name, created if missing. |
| items[].quantity* | integer | Quantity (1 or more). |
| items[].price* | number | Unit price the customer pays. |
| items[].product_id / variant_id / sku | string | Your ids. After the first order we remember which CRM product they are — even if the name changes later. |
| items[].options | object | Variation: {"Size": "L"}. Matched to the CRM's variations (case-insensitive). |
| discount | number | Order-level discount in GEL — spread over the items in proportion. |
| total | number | Order total. If it doesn't match, the seller sees a warning. |
| currency / note / admin_url | string | Currency (GEL), the customer's note, a link to the order in your admin. |
{
"id": "1001",
"number": "A-1001",
"created_at": "2026-10-15T13:30:00+04:00",
"customer": {
"name": "ნინო ბერიძე",
"phone": "+995 555 12 34 56",
"email": "nino@example.ge",
"personal_id": "01001012345"
},
"shipping": {
"city": "თბილისი",
"address": "ჭავჭავაძის გამზ. 12, ბინა 4",
"fee": 6,
"paid_by": "customer"
},
"payment": { "method": "cod", "title": "ადგილზე გადახდა", "paid": false },
"items": [
{
"product_id": "202",
"variant_id": "203",
"sku": "TS-L",
"name": "მაისური",
"options": { "ზომა": "L", "ფერი": "შავი" },
"quantity": 2,
"price": 35
}
],
"discount": 0,
"total": 76,
"currency": "GEL",
"note": "დარეკეთ მისვლამდე",
"admin_url": "https://shop.ge/admin/orders/1001"
}Retries are safe
If there's no answer, or a 429/5xx (retry: true), send the same order again. The same id is never saved twice — you get 200 with result: "duplicate". You may also send an Idempotency-Key header (8–100 chars).
Validate without saving
POST /api/v1/orders?dry_run=1 checks every field and shows the CRM order it would create, without saving anything. Invalid fields come back like this:
HTTP/1.1 422 Unprocessable Entity
{
"ok": false,
"error": "validation_failed",
"message": "Some fields are invalid. See `errors`.",
"field": "customer.phone",
"errors": [
{ "field": "customer.phone", "code": "required", "message": "customer.phone is required" },
{ "field": "items[0].quantity", "code": "invalid_value", "message": "items[0].quantity must be a whole number from 1 to 100000" }
],
"retry": false,
"server_time": 1760520601
}Order status
GET/api/v1/orders/{id}
{id} is your order id. Returns the same order object as on create. For status changes, webhooks are better than polling.
curl https://www.gayidvebi.ge/api/v1/orders/1001 \
-H "Authorization: Bearer gyd_live_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Cancel an order
POST/api/v1/orders/{id}/cancel
If the order is still in "საიტიდან", it is cancelled and the stock comes back. If the seller already confirmed it, a "⚠ cancelled on the site" note is added and the seller decides. The answer has the current status.
curl -X POST https://www.gayidvebi.ge/api/v1/orders/1001/cancel \
-H "Authorization: Bearer gyd_live_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "reason": "customer" }'Products & stock
GET/api/v1/products
The seller's products: price, variations and available stock (never cost). Parameters: limit (1–200, default 100), cursor (next_cursor of the previous answer), updated_since (ISO date — changed ones only). You may cache the answer for 60 seconds.
curl "https://www.gayidvebi.ge/api/v1/products?limit=100" \
-H "Authorization: Bearer gyd_live_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Webhooks
The seller enters your https URL in Settings and gets a signing secret (whsec_…). Every time the status of an order received through the API changes, that URL gets a POST. The format is Standard Webhooks (standardwebhooks.com): use their libraries or the code below.
order.status_changed— the status changed (confirming it too).order.deleted— the seller deleted the order.test— the "Send test" button in Settings.
POST /your/webhook/url HTTP/1.1
Content-Type: application/json
webhook-id: msg_5b0c3a1e9d2f4c7b8a6e1d0c9b8a7f6e
webhook-timestamp: 1760520601
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{
"type": "order.status_changed",
"timestamp": "2026-10-15T09:30:01.123Z",
"data": {
"id": "1001",
"number": "A-1001",
"crm_id": "5b0c…-…",
"status": { "id": "shipped", "label": "გაგზავნილი", "stage": "in_progress" },
"previous_status": { "id": "pending", "label": "ახალი", "stage": "new" },
"changed_at": "2026-10-15T09:30:01.123Z"
}
}| stage | Meaning |
|---|---|
| received | Received, waiting for the seller to confirm ("საიტიდან"). |
| new | Confirmed, being prepared. |
| in_progress | Any other column of the seller (e.g. "shipped"). label has its name. |
| completed | Delivered / completed. |
| cancelled | Cancelled (or returned). |
status.id and label are the seller's own columns and can change — program against stage.
Verify the signature
<?php
$secret = getenv('GAYIDVEBI_WEBHOOK_SECRET'); // whsec_...
$id = $_SERVER['HTTP_WEBHOOK_ID'] ?? '';
$ts = $_SERVER['HTTP_WEBHOOK_TIMESTAMP'] ?? '';
$header = $_SERVER['HTTP_WEBHOOK_SIGNATURE'] ?? '';
$body = file_get_contents('php://input'); // the raw body, not re-encoded
$key = base64_decode(substr($secret, strlen('whsec_')));
$expected = base64_encode(hash_hmac('sha256', "$id.$ts.$body", $key, true));
$valid = false;
foreach (explode(' ', $header) as $part) {
[$version, $signature] = array_pad(explode(',', $part, 2), 2, '');
if ($version === 'v1' && hash_equals($expected, $signature)) $valid = true;
}
if (!$valid || abs(time() - (int) $ts) > 300) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
// Retries reuse the same webhook-id: skip an id you already handled
if ($event['type'] === 'order.status_changed') {
$orderId = $event['data']['id']; // your order id
$stage = $event['data']['status']['stage']; // received|new|in_progress|completed|cancelled
// … update the order on your site
}
http_response_code(200);- Answer 2xx within 10 seconds. On any other answer or a timeout we retry after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h.
- Retries keep the same webhook-id — skip an id you already handled.
- Order isn't guaranteed: compare data.changed_at and skip older ones, or read GET /orders/{id}.
- Redirects (3xx) are not followed; the URL must be public https.
Errors
Errors look like {"ok": false, "error": "<code>", "message": "…", "retry": false}. retry: true means trying again later can help.
| error | HTTP | retry | Meaning |
|---|---|---|---|
| missing_api_key / invalid_api_key / unknown_key | 401 | false | The key is missing, malformed or unknown. |
| key_revoked | 401 | false | The key was revoked or replaced — create a new one in Settings. |
| browser_requests_not_allowed | 403 | false | The request came from a browser. Call from your server. |
| plan_not_allowed | 403 | false | The API needs the Pro plan or above. |
| key_in_url | 400 | false | The key is in the URL. Move it to the Authorization header. |
| invalid_json / invalid_idempotency_key / invalid_query / invalid_id | 400 | false | The request is malformed. |
| validation_failed / invalid_phone | 422 | false | Fields are invalid — see field and errors. |
| order_not_found | 404 | false | No order with this id was received from your site. |
| payload_too_large | 413 | false | The body is over 256 KB. |
| rate_limited | 429 | true | Over 120 requests a minute. Wait Retry-After seconds. |
| server_error / not_configured | 500 / 503 | true | Our side — try again later (the same id is safe). |
Questions? Напишите нам