پرش به محتوا

راهنمای توسعه‌دهنده · API تیکت‌ها

پشتیبانی، در صفحه‌های سایت خودتان

با API تیکت‌ها، صفحه «پشتیبانی» حساب کاربری سایت شما همان صفحه خودتان می‌ماند و پشت آن میز پشتیبانی پیکسول‌چت کار می‌کند: مشتری تیکت باز می‌کند، پاسخ پشتیبان را می‌خواند، دوباره می‌نویسد، گفت‌وگو را می‌بندد و امتیاز می‌دهد؛ پشتیبان‌ها همه را در پنل پیکسول‌چت می‌بینند.

احراز هویت

این API را فقط سرور سایت شما صدا می‌زند، نه مرورگر مشتری. کلید API همان کلیدی است که برای فرستادن مشتریان ساخته‌اید («تنظیمات کسب‌وکار ← اتصال به سایت ← کلید API») و با هر درخواست در سرآیند Authorization می‌آید:

http
GET /api/v1/contacts/u_123/conversations HTTP/1.1
Host: pixevel.chat
Authorization: Bearer sk_0123456789abcdef0123456789abcdef01234567

کلید، خودِ کسب‌وکار است: در هیچ نشانی شناسه کسب‌وکار نمی‌آید و هیچ درخواستی به کسب‌وکار دیگری نمی‌رسد. کلید را در کد صفحه‌ها نگذارید؛ مرورگر مشتری فقط با سرور شما حرف می‌زند و سرور شما با ما.

مشتری کیست و چه چیزی را می‌بیند

مشتری را با شناسه خودتان برای او نام می‌برید، همان externalId که با API مشتریان می‌فرستید (در نشانی، کدگذاری‌شده). سرور شما پیش از هر درخواست باید مطمئن باشد کاربر واردشده همین مشتری است.

هر مشتری فقط این گفت‌وگوها را می‌بیند:

  • گفت‌وگوهایی که خودش با ورود امضاشده در ابزارک شروع کرده است؛
  • گفت‌وگوهایی که با همین API برایش باز شده‌اند (channel: "api")؛
  • گفت‌وگوهایی که پشتیبان در پنل به او وصل کرده است: تیکتی که برای او ثبت شده، یا گفت‌وگویی که پشتیبان «وصل کردن به مشتری» را برایش زده است (openedByStaff برای تیکت‌های ثبت پشتیبان true است).

و هرگز این‌ها را نمی‌بیند:

  • گفت‌وگویی که فقط با شماره یا ایمیلی که بازدیدکننده‌ای در ابزارک نوشته به او وصل شده است؛ هر کسی ممکن است شماره مشتری شما را بنویسد؛
  • گفت‌وگویی که بازدیدکننده‌ای بدون ورود در ابزارک شروع کرده، حتی اگر پشتیبان آن را به او وصل کند: آن گفت‌وگو هنوز مال مرورگری است که شروعش کرد، و اگر اینجا هم نشان داده می‌شد، آن مرورگر نوشته‌های حساب مشتری را می‌خواند و نوشته‌هایش در حساب مشتری به نام خود او دیده می‌شد. اگر باید در حساب مشتری دیده شود، پشتیبان برایش تیکت تازه‌ای باز کند؛
  • گفت‌وگوهای تلگرام، بله و ایمیل، حتی اگر پشتیبان به او وصلشان کرده باشد؛ پاسخ آن‌ها همان‌جا فرستاده می‌شود.

گفت‌وگوی مشتری دیگر یا کسب‌وکار دیگر همیشه 404 NOT_FOUND است، هرگز 403. اگر پشتیبان پیوند را بردارد یا به مشتری دیگری بدهد، یا مشتری را حذف کنید، گفت‌وگو همان لحظه از دید او بیرون می‌رود؛ مشتری‌ای که دوباره با همان شناسه بفرستید، گفت‌وگوهای قبلی را پس نمی‌گیرد. پیوندهایی که پشتیبان پیش از این API ساخته به مشتری نشان داده نمی‌شوند؛ برای نشان دادنشان یک بار دیگر وصلشان کنید.

مسیرها

روشمسیرکار
GET/api/v1/categoriesدسته‌های فعال، به ترتیب پنل
GET/api/v1/contacts/{externalId}/conversationsفهرست گفت‌وگوهای مشتری
POST/api/v1/contacts/{externalId}/conversationsباز کردن گفت‌وگوی تازه (201)
GET/api/v1/contacts/{externalId}/conversations/{number}یک گفت‌وگو با پیام‌هایش؛ خوانده می‌شود
PATCH/api/v1/contacts/{externalId}/conversations/{number}بستن (resolved) یا بازکردن (open)
POST/api/v1/contacts/{externalId}/conversations/{number}/messagesپیام مشتری (201)
POST/api/v1/contacts/{externalId}/conversations/{number}/ratingامتیاز (201)
POST/api/v1/uploadsگرفتن جای بارگذاری یک فایل (201)
GET/api/v1/contacts/{externalId}/conversations/{number}/attachments/{id}خود فایل یک پیام

پاسخ‌ها مثل باقی API در پوشش { "ok": true, "data": … } می‌آیند و خطاها { "ok": false, "error": { "code", "message" } }. پیام خطاها انگلیسی است و نام فیلد را می‌گوید، مثل score: must be an integer from 1 to 5. number همان شماره‌ای است که مشتری می‌بیند (#42 یعنی 42).

گفت‌وگو و پیام

json
{
  "id": "0b7e6c2a-1f3d-4e5a-9b8c-000000000042",
  "number": 42,
  "kind": "ticket",
  "status": "pending",
  "channel": "api",
  "openedByStaff": false,
  "subject": "سفارش ۱۰۴۲ نرسیده",
  "category": { "id": "5d1c0a2e-0000-4000-8000-0000000000c7", "name": "پیگیری سفارش" },
  "orderExternalId": "ord-1042",
  "lastMessage": "پیگیری می‌کنیم.",
  "lastAuthor": "agent",
  "messageCount": 2,
  "unreadCount": 1,
  "awaitingCustomer": true,
  "canRate": false,
  "rating": null,
  "lastActivityAt": "2026-10-02T10:05:00Z",
  "resolvedAt": null,
  "createdAt": "2026-10-02T10:00:00Z"
}
  • messageCount پیام‌های عمومی است؛ یادداشت‌های داخلی پشتیبان هیچ‌جا نمی‌آیند.
  • unreadCount پاسخ‌های پشتیبان (یا دستیار) پس از آخرین باری است که مشتری گفت‌وگو را با این API خوانده است.
  • awaitingCustomer یعنی پشتیبان پاسخ داده و منتظر مشتری است (pending).
  • canRate یعنی گفت‌وگو حل یا بسته شده و این حل‌شدن هنوز امتیاز نگرفته است.

پیام‌ها { id, author, body, attachments, createdAt, editedAt } هستند (هر فایل { id, name, contentType, kind, sizeBytes, width, height, durationMs })؛ author یکی از customer، agent، assistant و system است. editedAt زمان آخرین ویرایش پشتیبان است (null اگر ویرایش نشده)؛ پیامی که پشتیبان حذف کرده اصلاً در فهرست نمی‌آید. شناسه پشتیبان، فرد مسئول، اولویت و شماره و ایمیل مشتری هرگز در پاسخ نیست.

باز کردن گفت‌وگو

http
POST /api/v1/contacts/u_123/conversations
Idempotency-Key: 7c2f1e0a-create
json
{
  "kind": "ticket",
  "subject": "سفارش ۱۰۴۲ نرسیده",
  "body": "سفارش ۱۰۴۲ من هنوز نرسیده.",
  "categoryId": "5d1c0a2e-0000-4000-8000-0000000000c7",
  "orderExternalId": "ord-1042",
  "contact": { "name": "مریم رضایی", "phone": "09121234567" }
}
  • kind یکی از ticket و live است؛ برای تیکت subject لازم است (حداکثر 200 نویسه). body حداکثر 5000 نویسه است و کوتاه نمی‌شود، رد می‌شود.
  • هر درخواست یک گفت‌وگوی تازه باز می‌کند؛ ادامه دادن گفت‌وگوی باز با پیام است.
  • گفت‌وگو مثل گفت‌وگوی ابزارک به پشتیبانِ مسئول دسته سپرده می‌شود، پشتیبان‌ها باخبر می‌شوند و وب‌هوک conversation.created فرستاده می‌شود.
  • orderExternalId شناسه سفارش در سیستم شماست (حداکثر 120 نویسه) و همان‌طور که فرستاده‌اید نگه داشته و برگردانده می‌شود.
  • اگر مشتری‌ای با این externalId نفرستاده باشید، از روی contact ساخته می‌شود (با همان قاعده‌های API مشتریان، و جزو سقف مشتریان شما). مشتری موجود هرگز با تیکت تغییر نمی‌کند: contact باز هم بررسی می‌شود (نادرستش در هر حال رد می‌شود)، اما به کار نمی‌رود.
  • اگر کسب‌وکار غیرفعال باشد یا اشتراکش معلق شده باشد، 403 DESK_UNAVAILABLE برمی‌گردد و هیچ چیزی، حتی مشتری، ساخته نمی‌شود.

پاسخ (201) خودِ گفت‌وگو و پیام‌هایش است: { conversation, messages }.

خواندن و تازه‌ها

GET …/conversations/{number} گفت‌وگو را با همه پیام‌های عمومی‌اش، از قدیمی به تازه، برمی‌گرداند و آن‌ها را خوانده می‌کند؛ unreadCount صفر می‌شود. پاسخ ساختن و پاسخ دادن هم همین کار را می‌کند. خواندن، زمان تغییر گفت‌وگو را جابه‌جا نمی‌کند و وب‌هوکی نمی‌فرستد.

جریان زنده نیست: برای دیدن پیام‌های تازه، با شناسه آخرین پیامی که دارید بپرسید:

http
GET /api/v1/contacts/u_123/conversations/42?after=7d1e2f30-0000-4000-8000-0000000000d1

پاسخ مشتری

POST …/messages با { "body": "…" } پیام مشتری را ثبت می‌کند. گفت‌وگو دوباره منتظر پشتیبان می‌شود و اگر حل شده بود، باز می‌شود و پشتیبانش باخبر می‌شود. پاسخ { conversation, message, messages } است و message همان پیامی است که این درخواست نوشت. گفت‌وگوی بسته پیام نمی‌گیرد: 409 CONVERSATION_CLOSED (یک گفت‌وگوی تازه باز کنید).

فایل‌ها

هر پیام تا 6 فایل دارد: تصویر، صدا، ویدیو، PDF، فایل فشرده یا متن. سه قدم:

  1. POST /api/v1/uploads با { "name": "screen.png", "contentType": "image/png", "sizeBytes": 48213 } جای بارگذاری می‌دهد: { uploadId, uploadUrl, expiresAt, maxBytes }.
  2. خود فایل را با PUT به uploadUrl بفرستید — بدون کلید؛ نشانی، خودش مجوز است و 5 دقیقه اعتبار دارد. سقف هر فایل 25 مگابایت و هر ویدیو 100 مگابایت است.
  3. uploadId را در attachments پیام بفرستید (POST …/conversations یا POST …/messages)؛ با فایل، body می‌تواند خالی باشد. هر بارگذاری یک بار و تا یک روز فرستادنی است.

نوع فایل از خود بایت‌ها خوانده می‌شود، نه از نام یا contentType. فایل یک پیام را با GET …/conversations/{number}/attachments/{id} و همان کلید بگیرید و برای مشتری بفرستید؛ نشانی را به مرورگر ندهید. فایل یادداشت داخلی، گفت‌وگوی مشتری دیگر یا کسب‌وکار دیگر 404 است. فضای فایل‌ها به پلن بسته است (PLAN_LIMIT با limit: storage).

بستن، بازکردن و امتیاز

PATCH …/conversations/{number} با { "status": "resolved" } گفت‌وگوی باز یا منتظر را حل‌شده می‌کند و با { "status": "open" } گفت‌وگوی حل‌شده را دوباره باز می‌کند (پشتیبان باخبر می‌شود). روی گفت‌وگویی که از قبل همان وضعیت را دارد کاری نمی‌کند؛ گفت‌وگوی بسته باز نمی‌شود (409 CONVERSATION_CLOSED).

POST …/rating با { "score": 5, "comment": "عالی" } امتیاز را ثبت می‌کند: score عددی صحیح از 1 تا 5 و comment حداکثر 1000 نویسه.

  • هر حل‌شدن یک امتیاز دارد. اگر گفت‌وگو دوباره باز شود (با پیام مشتری، پاسخ پشتیبان یا PATCH) و دوباره حل شود، دوباره می‌شود امتیاز داد.
  • پیش از حل شدن: 409 NOT_RESOLVED. امتیاز دوم برای همان حل‌شدن: 409 ALREADY_RATED.
  • هر امتیاز وب‌هوک conversation.rated را می‌فرستد (وب‌هوک‌ها).

بستن و امتیاز دادن وقتی اشتراک معلق است هم کار می‌کند؛ فقط گفت‌وگو و پیام تازه پذیرفته نمی‌شود.

فهرست و صفحه‌بندی

GET …/conversations گفت‌وگوها را از تازه‌ترین فعالیت می‌آورد: { items, nextCursor }. برای صفحه بعد، nextCursor را به‌عنوان cursor بفرستید؛ در صفحه آخر null است. پارامترها:

پارامترمعنا
kindticket یا live
statusفهرستی با ویرگول از open، pending، resolved و closed
cursornextCursor صفحه قبل
limit1 تا 100؛ پیش‌فرض 20

برای مشتری‌ای که هنوز نفرستاده‌اید، فهرست خالی است. گفت‌وگویی که در میانه ورق زدن فعالیت تازه بگیرد ممکن است به صفحه دیگری برود.

تکرار بی‌خطر با Idempotency-Key

به هر POST می‌توانید سرآیند Idempotency-Key بدهید (1 تا 255 نویسه ASCII دیدنی). اگر پاسخ در راه گم شد، همان درخواست را با همان کلید دوباره بفرستید:

  • کلید برای هر کسب‌وکار جداست و 24 ساعت معتبر است.
  • همان کلید با همان درخواست (روش، مسیر و بایت‌های بدنه) یک بار انجام می‌شود. تکرارش با 201 و سرآیند Idempotent-Replayed: true پاسخ می‌گیرد.
  • پاسخ تکرار از روی شناسه‌هایی که بار اول ثبت شد دوباره ساخته می‌شود، نه بایت‌های ذخیره‌شده: گفت‌وگو را همان‌طور که اکنون هست نشان می‌دهد، پاسخِ تکرار پیام همان پیام ثبت‌شده را در message دارد، و اگر پشتیبان در این میان پیوند گفت‌وگو را برداشته باشد، 404 می‌گیرد.
  • همان کلید با درخواست دیگر (بدنه، مسیر یا گفت‌وگوی دیگر) 422 IDEMPOTENCY_KEY_REUSED است و کاری انجام نمی‌شود.
  • درخواستی که رد شود یا شکست بخورد کلیدی ثبت نمی‌کند؛ همان کلید را دوباره به کار ببرید.
  • سرآیند خالی (یا فقط فاصله) 400 VALIDATION است، نه «بدون کلید»؛ اگر کلید ندارید، سرآیند را نفرستید.

محدودیت‌ها

  • این مسیرها: 1200 درخواست در دقیقه برای هر کلید.
  • مسیرهای مشتریان (/api/v1/me و /api/v1/contacts… بدون گفت‌وگو): 600 درخواست در دقیقه برای هر کلید، جدا از این‌ها؛ همگام‌سازی مشتریان و صفحه‌های پشتیبانی جای هم را تنگ نمی‌کنند.
  • بدنه حداکثر 1 مگابایت.
  • فایل: 6 در هر پیام؛ 25 مگابایت (ویدیو 100)؛ فایل بارگذاری‌شده‌ای که یک روز فرستاده نشود، پاک می‌شود.

خطاها

کدوضعیتچه وقت
UNAUTHORIZED401کلید نیست، اشتباه است یا عوض شده
VALIDATION400فیلد یا پارامتر نادرست؛ پیام، نام فیلد را می‌گوید
NOT_FOUND404گفت‌وگو نیست یا مال این مشتری نیست؛ یا categoryId: no such category
DESK_UNAVAILABLE403کسب‌وکار غیرفعال یا معلق است؛ گفت‌وگو و پیام تازه پذیرفته نمی‌شود
CONVERSATION_CLOSED409گفت‌وگوی بسته؛ یکی تازه باز کنید
NOT_RESOLVED409امتیاز پیش از حل شدن
ALREADY_RATED409این حل‌شدن امتیاز گرفته است
CONTACT_LIMIT409ساختن مشتری تازه از سقف مشتریان می‌گذرد
IDEMPOTENCY_KEY_REUSED422همان کلید با درخواست دیگر
PAYLOAD_TOO_LARGE413بدنه بزرگ‌تر از یک مگابایت
RATE_LIMITED429بیش از سهم دقیقه؛ پس از Retry-After ثانیه
ATTACHMENT_TOO_LARGE413فایل از سقف بزرگ‌تر است
ATTACHMENT_TYPE415نوع واقعی فایل پذیرفته نمی‌شود
PLAN_LIMIT403فضای فایل‌های پلن پر شده (limit: storage)
ATTACHMENTS_UNAVAILABLE503فایل فعلاً پذیرفته نمی‌شود؛ پیام بدون فایل کار می‌کند

از پشتیبانی فروشگاه به این API

اگر صفحه‌های پشتیبانی شما فیلدهای زیر را دارند، این‌ها جایشان را می‌گیرند:

فیلد فعلیاز این API
id / numbernumber
subjectFasubject
statusstatus با حروف بزرگ
orderIdorderExternalId
openedByStaffopenedByStaff
messageCountmessageCount
lastMessageAtlastActivityAt
unreadForUserunreadCount > 0
categorycategory.name
channelchannel
messages[].isStaffauthor یکی از agent و assistant
messages[].bodyFabody
امتیاز رضایتPOST …/rating
message در پاسخ ثبت پیامmessage
productIdندارد؛ اگر لازم است، نام کالا را در موضوع یا متن پیام بیاورید
pagination.total / pageندارد؛ صفحه‌بندی با cursor و nextCursor است و شمار کل گفت‌وگوها برنمی‌گردد
هویت مشتری را خودتان ثابت کنید

این API به هر externalId که سرور شما بفرستد اعتماد می‌کند. پیش از هر درخواست مطمئن شوید کاربر واردشده در سایت همان مشتری است؛ شناسه را هرگز از مرورگر نپذیرید.

API تیکت‌ها | پیکسول‌چت