مستندات API
با API می توانید از برنامه یا سایت خودتان با دستیار گفتگو کنید، مخاطب و سرنخ بخوانید، محصولات و متن دانش را به روز کنید و مصرف را ببینید. با وب هوک هم هر رویداد (گفتگوی جدید، پیام، سرنخ و ...) لحظه ای به سرور شما خبر داده می شود.
آدرس پایه:
https://api.harfyar.com/api/v1
احراز هویت
در پنل ← توسعه دهندگان یک کلید بسازید و دسترسی های لازم را انتخاب کنید. کلید را فقط سمت سرور نگه دارید (هرگز در کد جاوااسکریپت سایت).
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
| دسترسی | کاربرد |
|---|---|
conversations:read |
خواندن گفتگوها، پیام ها و فهرست دستیارها |
conversations:write |
ساخت گفتگو و ارسال پیام |
contacts:read |
خواندن مخاطبین و سرنخ ها |
contacts:write |
ثبت یا به روزرسانی مخاطب |
products:write |
ثبت، به روزرسانی و غیرفعال کردن محصول |
knowledge:write |
افزودن متن به دانش دستیار و به روزرسانی داده متغیر |
usage:read |
دیدن مصرف دوره جاری |
قواعد کلی
- همه درخواست ها و پاسخ ها JSON هستند (
Content-Type: application/json). - محدودیت: ۶۰ درخواست در دقیقه برای هر کلید. هدرهای
X-RateLimit-LimitوX-RateLimit-Remainingدر هر پاسخ هست؛ اگر بیشتر شد پاسخ429با هدرRetry-After(ثانیه) می آید. - تکرار امن: برای POST و PUT هدر
Idempotency-Key(یک رشته یکتا تا ۱۰۰ کاراکتر) بفرستید. اگر همان درخواست دوباره با همان کلید آمد (مثلا بعد از قطعی شبکه)، همان پاسخ قبلی با هدرIdempotent-Replayed: trueبرمی گردد و کاری دوبار انجام نمی شود. تا ۲۴ ساعت نگه داشته می شود. - شناسه ها: همه شناسه ها UUID هستند.
- صفحه بندی: فهرست ها با
?after=<شناسه آخرین مورد>وlimit(حداکثر ۱۰۰) صفحه بندی می شوند؛next_cursorدر پاسخ همان شناسه آخرین مورد است. - مبالغ همیشه به ریال و عدد صحیح هستند.
خطاها
همه خطاها یک شکل دارند:
{
"error": {
"code": "validation_failed",
"message": "The request is not valid.",
"request_id": "01JABCDXYZ...",
"details": { "text": ["The text field is required."] }
}
}
| کد | وضعیت HTTP | معنی |
|---|---|---|
unauthenticated |
401 | کلید نیست یا اشتباه یا باطل شده است |
forbidden_ip |
403 | کلید برای IP دیگری تنظیم شده |
plan_required |
403 | پلن فعلی API ندارد |
workspace_inactive |
403 | حساب تعلیق شده است |
insufficient_scope |
403 | کلید این دسترسی را ندارد |
limit_reached |
403 | به سقف پلن رسیده اید (محصول، دانش) |
not_found |
404 | پیدا نشد (یا مال حساب دیگری است) |
validation_failed |
422 | ورودی نادرست؛ جزئیات در details |
invalid_message |
422 | پیام خالی یا بیش از ۲۰۰۰ کاراکتر |
idempotency_conflict |
409 | همان Idempotency-Key با درخواست متفاوت |
rate_limited |
429 | درخواست زیاد |
server_error |
500 | خطای ما؛ request_id را برای پشتیبانی بفرستید |
دستیارها
GET /bots
{ "data": [ { "id": "9b1c...", "name": "دستیار فروشگاه", "status": "active" } ] }
گفتگو
پاسخ دستیار در پس زمینه ساخته می شود (چند ثانیه). بعد از ارسال پیام، پیام های جدید را با GET .../messages?after= بگیرید یا رویداد وب هوک message.created را گوش دهید.
POST /conversations
| فیلد | نوع | توضیح |
|---|---|---|
bot_id |
uuid | الزامی |
visitor.external_id |
string | شناسه کاربر در سیستم شما؛ همان شناسه همیشه همان بازدیدکننده است |
visitor.name، visitor.mobile، visitor.email |
string | اختیاری؛ مخاطب ساخته یا به روز می شود |
message |
string | اختیاری؛ اولین پیام |
{
"data": {
"id": "3f2a...",
"bot_id": "9b1c...",
"channel": "api",
"status": "open",
"subject": null,
"contact_id": "77aa...",
"page_url": null,
"created_at": "2026-10-10T08:30:00+00:00"
}
}
POST /conversations/{id}/messages
{ "text": "هزینه ارسال به شیراز چقدر است؟", "client_id": "order-42-q1" }
client_id (اختیاری) جلوی ثبت دوباره همان پیام را می گیرد. پاسخ 202:
{ "data": { "id": "c1d2...", "conversation_id": "3f2a...", "from": "visitor", "body": "هزینه ارسال ...", "attachments": 0, "created_at": "..." } }
GET /conversations/{id}/messages?after={message_id}
پیام های بعد از یک پیام (قدیمی به جدید). from یکی از visitor، ai، agent (کارشناس) یا system است.
{ "data": [ { "id": "...", "from": "ai", "body": "ارسال به شیراز ...", "created_at": "..." } ], "next_cursor": "...", "status": "open" }
GET /conversations/{id}
مخاطبین و سرنخ ها
GET /contacts?after=&mobile=09121234567POST /contactsباname،mobile(یاexternal_id)،email: اگر مخاطبی با همان موبایل (یا external_id) باشد به روز می شود (200) وگرنه ساخته می شود (201).GET /leads?after=&stage=new— مرحله ها:new،contacted،qualified،won،lost
{
"id": "…", "stage": "new", "need_summary": "خرید یخچال ساید بای ساید", "score": 70,
"bot_id": "…", "conversation_id": "…",
"contact": { "id": "…", "name": "علی", "mobile": "09121234567", "email": null, "external_id": null, "created_at": "…" },
"created_at": "…"
}
محصولات
PUT /products/{external_id}
external_id شناسه محصول در سیستم شماست (حروف، عدد، _ . : -).
{
"bot_id": "9b1c...",
"title": "دریل شارژی ۱۸ ولت",
"url": "https://shop.ir/p/12",
"image_url": "https://shop.ir/i/12.jpg",
"price_rial": 45000000,
"sale_price_rial": 42000000,
"in_stock": true,
"category": "ابزار برقی",
"description": "دو باتری، کیف حمل",
"priority": 0
}
POST /products/batch
تا ۱۰۰ محصول در یک درخواست (همان فیلدهای PUT به اضافه external_id در هر مورد). محصولاتی که به خاطر سقف پلن ذخیره نشدند در skipped می آیند.
{ "bot_id": "9b1c...", "items": [ { "external_id": "SKU-12", "title": "دریل شارژی", "price_rial": 45000000 } ] }
{ "data": { "saved": 1, "skipped": [] } }
DELETE /products/{external_id}?bot_id=...
محصول غیرفعال می شود (دستیار دیگر پیشنهادش نمی دهد).
دانش
POST /knowledge/texts
{ "bot_id": "9b1c...", "title": "شرایط ارسال", "text": "ارسال به تهران یک روزه ...", "external_id": "page-shipping" }
با همان external_id متن قبلی جایگزین می شود. status تا آماده شدن processing است و بعد ready.
DELETE /knowledge/texts/{external_id}?bot_id=...
متن و بردارهای آن از دانش حذف می شود.
داده متغیر
اطلاعات کوتاهی که زیاد عوض می شود (قیمت روز، هزینه ارسال، ساعت کاری امروز، تخفیف این هفته). دستیار این ها را همراه هر پاسخ می بیند، عین مقدار را به مشتری می گوید و اگر با متن های قدیمی تر دانش فرق داشت، همین را درست می داند. هر تغییر مقدار، پاسخ های ذخیره شده قبلی را هم باطل می کند. همان دسترسی knowledge:write لازم است.
key: شناسه ثابت شما برای این داده؛ حروف و اعداد لاتین و_ . : -تا ۶۴ کاراکتر (بزرگی و کوچکی حروف فرقی ندارد).- سقف ها: حداکثر ۳۰ داده برای هر دستیار؛ عنوان تا ۶۰، مقدار تا ۲۰۰ و توضیح تا ۱۵۰ کاراکتر؛ مجموع همه داده های یک دستیار تا ۳۰۰۰ کاراکتر. اگر بیشتر شود خطای
validation_failedبرمی گردد.
PUT /knowledge/variables/{key}
ساخت یا به روزرسانی. label فقط برای ساخت لازم است؛ اگر note نفرستید، توضیح قبلی می ماند (با null پاک می شود). ارسال همان مقدار قبلی فقط زمان «آخرین به روزرسانی» را تازه می کند.
{ "bot_id": "9b1c...", "label": "قیمت دلار", "value": "62,500 تومان", "note": "نرخ فروش امروز" }
{ "data": { "key": "usd_rate", "label": "قیمت دلار", "value": "62,500 تومان", "note": "نرخ فروش امروز", "updated_at": "2026-10-20T06:30:00+00:00", "bot_id": "9b1c..." } }
پاسخ 201 برای داده تازه و 200 برای به روزرسانی.
GET /knowledge/variables?bot_id=...
فهرست همه داده های متغیر دستیار (همان شکل بالا، بدون صفحه بندی).
DELETE /knowledge/variables/{key}?bot_id=...
{ "data": { "key": "usd_rate", "deleted": true } }
مصرف
GET /usage
{ "data": { "plan": "growth", "period_start": "...", "period_end": "...", "ai_replies_used": 1240, "ai_replies_allowance": 5000, "addon_remaining": 0 } }
وب هوک
در پنل ← توسعه دهندگان آدرس HTTPS و رویدادها را انتخاب کنید. رمز امضا (whsec_...) فقط یک بار نمایش داده می شود.
| رویداد | کی |
|---|---|
conversation.created |
گفتگوی جدید (ویجت، API، تلگرام و ...) |
message.created |
هر پیام بازدیدکننده، دستیار یا کارشناس (یادداشت داخلی نه) |
handoff.requested |
گفتگو به کارشناس منتقل شد |
lead.created |
سرنخ جدید |
form.submitted |
فرمی ارسال شد |
بدنه درخواست:
{
"id": "5e0f...",
"event": "lead.created",
"created_at": "2026-10-10T08:31:00+00:00",
"data": { "…": "همان شکل اشیای بالا" }
}
هدرها: X-Webhook-Id (همان id؛ برای جلوگیری از پردازش تکراری)، X-Webhook-Event، X-Webhook-Timestamp (ثانیه)، و:
X-Signature: sha256=HMAC_SHA256(secret, timestamp + "." + body)
در ۱۰ ثانیه با وضعیت 2xx پاسخ دهید؛ وگرنه تا ۶ بار (بعد از ۱ دقیقه، ۵ دقیقه، ۳۰ دقیقه، ۲ ساعت و ۶ ساعت) دوباره فرستاده می شود. بعد از ۲۰ شکست پشت سر هم وب هوک غیرفعال و به شما اطلاع داده می شود. دکمه «ارسال آزمایشی» رویداد ping می فرستد.
بررسی امضا — PHP
$body = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$expected = 'sha256='.hash_hmac('sha256', $timestamp.'.'.$body, getenv('CHAT_WEBHOOK_SECRET'));
if (! hash_equals($expected, $_SERVER['HTTP_X_SIGNATURE'] ?? '') || abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
// $event['id'] را ذخیره کنید و اگر قبلا دیده اید دوباره پردازش نکنید.
http_response_code(200);
بررسی امضا — Node.js
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.post('/webhooks/chat', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('X-Webhook-Timestamp') ?? '';
const expected = 'sha256=' + crypto.createHmac('sha256', process.env.CHAT_WEBHOOK_SECRET)
.update(timestamp + '.' + req.body).digest('hex');
const given = req.get('X-Signature') ?? '';
const valid = given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
if (!valid || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.sendStatus(401);
const event = JSON.parse(req.body);
console.log(event.event, event.data);
res.sendStatus(200);
});
نمونه کامل — PHP (Laravel HTTP یا Guzzle)
use Illuminate\Support\Facades\Http;
$api = Http::baseUrl('https://api.harfyar.com/api/v1')
->withToken(env('CHAT_API_KEY'))
->acceptJson();
$conversation = $api->withHeaders(['Idempotency-Key' => 'user-42-start'])
->post('conversations', [
'bot_id' => env('CHAT_BOT_ID'),
'visitor' => ['external_id' => 'user-42', 'name' => 'علی', 'mobile' => '09121234567'],
'message' => 'سلام، سفارشم کی می رسد؟',
])->throw()->json('data');
// چند ثانیه بعد
$messages = $api->get("conversations/{$conversation['id']}/messages")->throw()->json('data');
foreach ($messages as $m) {
echo "{$m['from']}: {$m['body']}\n";
}
نمونه کامل — JavaScript (Node.js 18+)
const base = 'https://api.harfyar.com/api/v1';
const headers = { Authorization: `Bearer ${process.env.CHAT_API_KEY}`, 'Content-Type': 'application/json' };
async function call(method, path, body, idempotencyKey) {
const res = await fetch(base + path, {
method,
headers: idempotencyKey ? { ...headers, 'Idempotency-Key': idempotencyKey } : headers,
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (!res.ok) throw new Error(`${json.error.code}: ${json.error.message} (${json.error.request_id})`);
return json;
}
const { data: conversation } = await call('POST', '/conversations', {
bot_id: process.env.CHAT_BOT_ID,
visitor: { external_id: 'user-42' },
}, 'user-42-start');
const { data: sent } = await call('POST', `/conversations/${conversation.id}/messages`, { text: 'ساعت کاری شما؟' });
// Poll every 2 seconds until the bot answers.
let after = sent.id;
for (let i = 0; i < 15; i++) {
await new Promise((r) => setTimeout(r, 2000));
const page = await call('GET', `/conversations/${conversation.id}/messages?after=${after}`);
for (const m of page.data) console.log(`${m.from}: ${m.body}`);
if (page.next_cursor) after = page.next_cursor;
if (page.data.some((m) => m.from !== 'visitor')) break;
}
نمونه — cURL
curl -X PUT https://api.harfyar.com/api/v1/products/SKU-12 \
-H "Authorization: Bearer $CHAT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sku-12-2026-10-10" \
-d '{"bot_id":"9b1c...","title":"دریل شارژی","price_rial":45000000,"in_stock":true}'