مرجع API
دو سطح، دو نوع کلید، دو میزبان. این صفحه میگوید کدام کار با کدامیک انجام میشود.
سگمنتیک روی دو میزبان جواب میدهد. این دو هیچ چیز مشترکی ندارند: نه کلید، نه شکل درخواست، نه شکل خطا. درخواستی که به میزبان اشتباه برود با خطای احراز هویت رد میشود و آن خطا چیزی درباره اشتباه شما نمیگوید، پس اولین چیزی که باید درست بدانید این است که هر کار روی کدام میزبان انجام میشود.
دو سطح
| ورود داده | مدیریتی | |
|---|---|---|
| میزبان | https://in.segmentic.net | https://api.segmentic.net |
| کلید | wk_seg_... | sk_seg_... |
| کلید کجا زندگی میکند | داخل اپ شما: باندل جاوااسکریپت، APK، IPA | روی سرور خودتان، در یک متغیر محیطی |
| چه کسی صدایش میزند | دستگاه کاربران شما | بکاند خودتان، یک اسکریپت، یک عامل هوش مصنوعی |
| چه کار میکند | رویداد و دستگاه و نشانی را مینویسد | آنچه در حساب هست را میخواند و عوض میکند |
| مرجع | نقاط ورود داده | API مدیریتی |
پنل، یعنی https://app.segmentic.net، میزبان سوم است و یک وبسایت است نه API. آن APIای که پنل با آن حرف میزند روی listener جداگانهای است که اصلا از اینترنت مسیریابی نمیشود، و دلیلش این است: دو مسیری که در بدنه پاسخشان یک اعتبارنامه متنی برمیگردانند آنجا زندگی میکنند، و نبودن یک آدرس بهتر از چکی است که ممکن است کسی یادش برود بنویسد.
هر مسیر روی هر دو سطح زیر /v1/ است. نه بخش /api وجود دارد و نه هیچ پیشوند دیگری.
دو نوع کلید
هر دو کلید ۳۲ بایت از منبع تصادفی سیستمعاملاند، با base64url بدون padding کد شدهاند و یک پیشوند جلویشان دارند. پس یک کلید واقعی wk_seg_ یا sk_seg_ است بهعلاوه ۴۳ کاراکتر. از هر کلید فقط هش SHA-256 ذخیره میشود، و به همین دلیل هیچکدام بار دوم به شما نشان داده نمیشوند: یک دامپ لو رفته دیتابیس نباید یک انبار کلید کارآمد باشد.
دو پیشوند از هم جدایند تا یک نشتی در یک نگاه دستهبندی شود. wk_ داخل یک باندل عمومی همان چیزی است که باید باشد. sk_ در همان جا یک حادثه امنیتی است.
کلید نوشتن wk_seg_ | کلید API sk_seg_ | |
|---|---|---|
| چه چیزی را معرفی میکند | یک اپ داخل یک حساب | یک کلید داخل یک حساب، با یک نقش |
| مجوزها | هیچ، و هیچ مجوزی هم ممکن نیست | هرچه نقشش میدهد |
| داده یک آدم را میخواند | نه، جز صندوق درونبرنامهای خود همان آدم که کلید دومی میخواهد | بله، اگر نقش اجازه بدهد |
| عمومی است | بله، عمدا | نه. راز است |
| انقضا | ندارد | پیشفرض ۳۶۵ روز، هنگام ساخت انتخاب میشود |
| ابطال | برای هر اپ، در پنل | برای هر کلید، در پنل |
کلید نوشتن به یک حساب، یک اپ و محیط همان اپ (development یا staging یا production) ترجمه میشود. همین. نمیتواند پروندهای بخواند، سگمنتی فهرست کند، مخاطبی بشمارد یا پیامی بفرستد، و هیچ تنظیمی هم نیست که چنین اجازهای بدهد.
کلید API به یک نقش ترجمه میشود: admin، marketer، analyst، viewer، approver یا finance. نقش مجوزها را تعیین میکند و مجوزها مسیرها را. GET /v1/whoami فهرست موثر را برمیگرداند، و پاسخ 403 در فیلد need دقیقا نام همان مجوزی را میآورد که نداشتید، تا کسی مجبور نشود برای فهمیدن اینکه کدام مجوز را باید بدهد تیکت بزند.
گرفتن هر کلید
کلید نوشتن از پنل میآید، از صفحه «SDK و اتصال». اپ را انتخاب کنید، کلید بسازید، متن کلید را بردارید. هر اپ کلید خودش را دارد تا ابطال یکی بقیه را ساکت نکند، و متن کلید فقط یک بار روی صفحه میآید و بعد از آن هرگز قابل بازیابی نیست.
کلید API از پنل میآید، از «تنظیمات» و بعد «کلیدهای API». نقش را هنگام ساخت انتخاب میکنید و کلید تا آخر عمرش همان نقش را دارد. سه قاعده که همان صفحه اعمال میکند: نقش owner رد میشود، پس هیچ کلیدی هرگز نمیتواند حساب را منتقل یا حذف کند؛ کلید نمیتواند از سازندهاش بالاتر باشد؛ و انقضای خالی ۳۶۵ روز میشود، نه «هرگز».
هیچکدام از این دو کلید از طریق API مدیریتی ساخته نمیشوند. هر دو مسیر ساخت روی listener خود داشبورد هستند که عمدا از بیرون مسیریابی نمیشود، پس ساختن اعتبارنامه کاری است که یک آدم واردشده در پنل انجام میدهد. این عمدی است و قرار نیست شل شود.
کلید محدودشده (scoped) کار نمیکند. دیتابیس روی هر کلید ستون scopes دارد، مسیر خواندن هم آن را میخواند، و هیچجای محصول هرگز در آن نمینویسد. پس هر کلید تمام نقشش را دارد و GET /v1/whoami همیشه "scoped": false جواب میدهد. باریکترین کلیدی که واقعا میتوانید بسازید، باریکترین نقش است. سقف گیرنده و سقف ردیف اطلاعات شخصی روی همان جدول هم همین وضع را دارند: ستون هست، کدی که بخواندش نیست.
کلید API هرگز به دست کلاینت نمیرسد
کلید نوشتن عمدا عمومی است. داخل جاوااسکریپت شما میرود، هرکسی میتواند آن را از سورس صفحه بخواند، و این پذیرفتنی است چون بدترین کاری که یک غریبه با آن میکند اضافهکردن نویز به داده خود شماست، که هم دیده میشود و هم قابل تعمیر است.
کلید API برعکس است. میتواند مخاطبهای شما را بشمارد، شماره تلفن مشتریهایتان را خروجی بگیرد و به همهشان پیام بفرستد. جایش روی سرور شماست، در یک متغیر محیطی، و هیچ جای دیگری نیست. نه داخل اپ موبایل، که از باینری بیرون کشیده میشود. نه داخل مرورگر، که یک view-source با آن فاصله دارد. نه داخل فایل کانفیگ بیلد اپ موبایل، که همان کار است با یک قدم اضافه.
جابهجا فرستادن این دو آنقدر رایج است که هر دو جهت جواب مشخصی دارند، و فقط یکی از آن دو به شما میگوید چه اشتباهی کردهاید.
کلید wk_ که به میزبان مدیریتی برود، 401 با کد مخصوص خودش میگیرد:
{
"error": {
"code": "write_key_rejected",
"message": "that is an SDK write key (wk_…); this API needs a management key (sk_seg_…)"
}
}
این رد شدن پیش از هر مراجعه به دیتابیس اتفاق میافتد، فقط از روی پیشوند.
کلید sk_ که به میزبان ورود داده برود، 401 با همان جواب همیشگی میگیرد:
{"status":"error","message":"invalid write key"}
کالکتور اصلا به پیشوند نگاه نمیکند. هرچه گرفته را هش میکند و هش را در جدول کلیدهای نوشتن میگردد، و کلید API آنجا نیست، پس از کلیدی که باطل شده یا هرگز وجود نداشته قابل تشخیص نیست. این یکیشدن عمدی است: این نقطه نباید ابزاری برای فهمیدن اینکه چه کلیدهایی وجود دارند بشود. هزینهاش این است که این جهت هیچ سرنخی به شما نمیدهد، و اگر به invalid write key خیره شدهاید با کلیدی که مطمئنید درست است، اول پیشوندش را نگاه کنید.
کدام کار روی کدام میزبان
| کار | میزبان | مسیر |
|---|---|---|
| ثبت اینکه یک آدم چه کرد | ورود داده | POST /v1/track |
| گذاشتن ویژگی روی یک پرونده | ورود داده | POST /v1/identify |
| ثبت دیدهشدن یک صفحه یا یک اسکرین | ورود داده | POST /v1/page، POST /v1/screen |
| چسباندن تاریخچه ناشناس به آدم واردشده | ورود داده | POST /v1/alias |
| فرستادن چند رویداد با هم از دستگاه | ورود داده | POST /v1/batch |
| فرستادن رویداد از سرور خودتان | مدیریتی | POST /v1/events |
| ثبت یک دستگاه برای پوش | ورود داده | POST /v1/devices |
| مشترککردن یک مرورگر در وبپوش | ورود داده | POST /v1/webpush/subscribe |
| وصلکردن چت بله، ایتا یا روبیکا | ورود داده | POST /v1/messenger/link |
| خواندن صندوق درونبرنامهای کاربر واردشده | ورود داده | POST /v1/inbox |
| گرفتن کمپینهای روی سایت برای یک صفحه | ورود داده | GET /v1/onsite |
| اعتبارسنجی یا شمارش مخاطب | مدیریتی | POST /v1/audiences/validate، POST /v1/audiences/count |
| ساخت، تغییر یا حذف سگمنت | مدیریتی | POST /v1/segments، PUT /v1/segments/{id}، DELETE /v1/segments/{id} |
| ساخت کمپین و بعد فرستادنش | مدیریتی | POST /v1/campaigns، POST /v1/campaigns/{id}/send |
| گرفتن گزارش قیف یا ماندگاری | مدیریتی | POST /v1/reports/funnel، POST /v1/reports/retention |
| فرستادن یک پیام تراکنشی | مدیریتی | POST /v1/messages |
| صفکردن یک خروجی | مدیریتی | POST /v1/exports |
| پرسیدن اینکه یک کلید چه میتواند بکند | مدیریتی | GET /v1/whoami |
| پرسیدن اینکه این نصب چه چیزهایی را سرو میکند | مدیریتی | GET /v1/capabilities |
| بررسی بالا بودن یک میزبان | هر دو | GET /v1/status |
POST /v1/batch روی میزبان ورود داده و POST /v1/events روی میزبان مدیریتی هر دو یک آرایه از رویداد میگیرند و بهجای هم استفاده نمیشوند. کلید آرایه در یکی batch است و در دیگری events. اولی 200 جواب میدهد و با message_id تکراریها را حذف میکند؛ دومی 202 جواب میدهد و اصلا تکراریها را حذف نمیکند، پس بستهای که دوباره فرستاده شود دو بار شمرده میشود. دومی هر زمانی قدیمیتر از ۳۰ روز را هم بهجای رد کردن، دقیقا روی مرز ۳۰ روز پیش میچسباند، که مهاجرت تاریخچه را بیصدا خراب میکند. هر دو در صفحه خودشان کامل توضیح داده شدهاند.
قراردادهای مشترک
JSON در هر دو جهت. هر پاسخ روی هر دو سطح با Content-Type: application/json; charset=utf-8 میآید. هیچکدام از دو سطح نوع محتوای خود درخواست را بررسی نمیکند: هر دو بدنه را میخوانند و هرچه ادعا شده باشد، آن را JSON میخوانند. با این حال application/json بفرستید، چون همان چیزی است که روزی بررسی خواهد شد.
زمانها فقط RFC 3339. هر فیلد زمانی روی سیم، یعنی timestamp و sent_at و scheduled_at و from و to در بازه گزارش، با دیکودر استاندارد JSON زبان Go خوانده میشود که فقط RFC 3339 را میپذیرد. ثانیه یونیکس، میلیثانیه یونیکس و تاریخ خالی مثل 2026-08-06 هیچکدام دیکود نمیشوند، و روی میزبان ورود داده این یعنی کل درخواست با malformed JSON رد میشود. زمان را UTC بفرستید.
متن UTF-8 است و فارسی هنگام ورود یکدست میشود. حرفهای عربی شبیهبهفارسی به شکل فارسی برگردانده میشوند، یعنی ي به ی و ك به ک، اعراب و کشیده حذف میشوند و فاصلههای عجیب به یک فاصله ساده جمع میشوند. نیمفاصله، بزرگی و کوچکی حروف لاتین و ارقام فارسی دقیقا همانطور که فرستاده شدهاند میمانند. به همین دلیل است که سگمنتی روی «شهر = تهران» کاربری را هم میگیرد که کیبوردش عربی بوده.
شناسههایی که میفرستید رشتهاند و شناسههایی که برمیگردانیم عدد. user_id و anonymous_id و message_id رشتههایی حداکثر ۲۵۶ بایتیاند و هیچ قاعده قالبی ندارند. شناسه سگمنت، کمپین یا خروجی در JSON عدد صحیح بدون علامت است، و شکل داخل مسیر باید عدد صحیح مثبت باشد وگرنه جواب 400 است.
هیچ چیزی در بدنه نمیتواند حساب را عوض کند. هر دو سطح حساب را از روی اعتبارنامه برمیدارند و هرچه بدنه گفته باشد را بازنویسی میکنند. tenant_id داخل بدنه یک سگمنت رد نمیشود، فقط هرگز خوانده نمیشود.
دو پوشش خطا
میزبان ورود داده خطا را در همان پوششی میدهد که موفقیت را میدهد:
{"status":"error","message":"request body too large"}
میزبان مدیریتی یک شیء با کد پایدار میدهد:
{
"error": {
"code": "forbidden",
"message": "this key does not carry data.export, see GET /v1/whoami for what it does carry",
"need": "data.export"
}
}
code قرارداد است. message قرارداد نیست، و هر یکپارچهسازی که روی متن پیام شرط بگذارد اولین باری که جمله بهتر نوشته شود میشکند. details روی بعضی خطاهای اعتبارسنجی میآید و همان تکه از payload شما را که مشکل دارد نشان میدهد. need فقط روی 403 میآید.
روی میزبان مدیریتی این پوشش یکدست نیست، برخلاف کامنتی که در خود سورس میگوید هست. یازده تا از بیستودو مسیر از هندلرهایی استفاده میکنند که برای پنل نوشته شده بودند و {"error":"count unavailable"} جواب میدهند، یعنی error رشته است نه شیء، و دو مسیر گزارش به فارسی جواب میدهند. محتاطانه parse کنید: error را بخوانید، ببینید رشته است یا شیء، و هرگز فرض نکنید error.code وجود دارد. مرجع API مدیریتی میگوید کدام مسیر کدام شکل را میدهد.
صفحهبندی
صفحهبندی به معنای واقعی وجود ندارد، و این همان حفرهای است که بیشتر از همه ممکن است یک بعدازظهر از شما بگیرد.
?limit= دقیقا روی یک مسیر خوانده میشود، GET /v1/exports، که پیشفرضش ۲۵ است و روی ۱۰۰ سقف میخورد. ?cursor= همانجا خوانده میشود و بعد دور ریخته میشود. بقیه مسیرهای فهرست هر دو پارامتر را نادیده میگیرند. هیچ هندلری next_cursor تولید نمیکند، پس این کلید بهجای اینکه باشد و تهی بماند، اصلا در بدنه هیچ پاسخی نیست، و has_more همیشه false است، حتی وقتی ردیف بیشتری هست.
GET /v1/segments و GET /v1/campaigns از «بدون صفحهبندی» هم بدترند: کوئری پشتشان به ORDER BY updated_at DESC LIMIT 200 ختم میشود و هیچچیز در پاسخ این را نمیگوید. نه تعدادی هست، نه has_more، نه هشداری. حسابی که ۲۵۰ سگمنت دارد، ۲۰۰ تای تازهتر را میگیرد و روی هیچ سطحی مسیری نیست که به آن ۵۰ تای دیگر برسد. این دو فهرست را «۲۰۰ تای آخر» بخوانید و هرچه لازم دارید را با شناسهاش قابل دسترس نگه دارید.
سقفها و بودجه
میزبان ورود داده هیچ محدودیت نرخی ندارد. نه در ثانیه، نه در دقیقه، نه بهازای کلید، نه بهازای IP. تنها کنترل حجم روی آن سهمیه ماهانه صورتحساب است، و وقتی آن تمام شود جواب 402 Payment Required است با یک جمله فارسی و رد شدن کل درخواست. SDK نباید 402 را دوباره امتحان کند: تا کسی پول ندهد چیزی عوض نمیشود.
میزبان مدیریتی با وزن اندازه میگیرد، نه با تعداد. هر کلید در هر دقیقه تقویمی ۶۰۰ واحد دارد. تماسی که چیزی نمیخواند ۱ واحد است، یک کوئری محدود روی انبار داده ۵ واحد، و اسکنی که با تاریخچه شما بزرگ میشود ۲۵ واحد. بودجه بهازای کلید است نه حساب، تا یک عامل هوش مصنوعی که در حلقه افتاده نتواند بودجهای را که خط لوله سفارشهای شما به آن وابسته است تمام کند. تمامشدن بودجه 429 است با Retry-After: 60:
{"error":{"code":"budget_exhausted","message":"this key has spent its request budget for the minute"}}
روی بودجه هیچ هدر X-RateLimit-* وجود ندارد و GET /v1/whoami هم نمیگوید چقدر مانده، پس کلاینت تا وقتی رد نشده نمیبیند چقدر نزدیک است. تنها هدرهای نرخ روی کل این سطح، X-RateLimit-Limit و X-RateLimit-Remaining روی POST /v1/messages هستند، و آنها محدودکننده دوم و جداگانهای را توصیف میکنند که بهازای حساب است و درخواست میشمارد نه وزن.
بودجه بسته شکست میخورد. اگر شمارنده در دسترس نباشد جواب 503 با کد budget_unavailable است، حتی روی GET /v1/whoami و GET /v1/capabilities، چون آنها هم بودجه خرج میکنند. GET /v1/status تنها مسیری است که از آن قطعی جان سالم به در میبرد. این جهت عمدی است: یک عامل بیاندازهگیری که در حلقه افتاده گرانتر از گزارشی است که منتظر میماند.
بررسی بالا بودن یک میزبان
هر دو میزبان GET /v1/status را بدون اعتبارنامه، بدون خواندن دیتابیس و بدون محدودیت نرخ سرو میکنند.
curl -i https://in.segmentic.net/v1/status
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Cache-Control: no-store, no-cache, must-revalidate
X-Server-Time: 2026-08-07T09:12:41Z
{"status":"ok","service":"collector","version":"1.42.0"}
service روی میزبان ورود داده collector است و روی میزبان مدیریتی api، تا یک صفحه وضعیت بتواند برای هر مولفه یک چراغ نشان بدهد. version مهر بیلد است، که «آیا استقرار رفته بالا» را از بیرون قابل جوابدادن میکند. X-Server-Time هست تا پروبی که زمان رفتوبرگشت را میسنجد بتواند «ما کندیم» را از «مسیر بین شما و ما کند است» جدا کند. بدنه هرگز کش نمیشود، چون صفحه وضعیتی که وسط یک قطعی یک ok کششده را از CDN میخواند از نبودن صفحه وضعیت بدتر است.
توسعه محلی
کالکتور بهطور پیشفرض روی http://localhost:8080 گوش میدهد (متغیر HTTP_ADDR). هرچه روی میزبان ورود داده هست، آنجا با یک کلید نوشتن از حساب محلی شما کار میکند.
API مدیریتی روی هر آدرسی که PUBLIC_API_ADDR بگوید گوش میدهد، و این متغیر بهطور پیشفرض خالی است، یعنی تا کسی مقدارش را نگذارد API مدیریتی اصلا سرو نمیشود. نشانهاش connection refused است نه 404، و اولین چیزی است که وقتی یکپارچهسازی سمت سرور شما به هیچجا نمیرسد باید ببینید. استقرار آن را به :8082 میبندد، که همان آدرسی است که سرور MCP هم وقتی SEGMENTIC_API_URL تنظیم نشده باشد فرض میکند.
https://in.segmentic.ir میزبان ورود داده نیست و عمدا به میزبان فعلی ریدایرکت نمیشود. SDKای که هنوز به نام قدیمی اشاره میکند باید با صدای بلند خراب شود، نه اینکه کار کند.
چیزهایی که اینجا نیستند
اینها را مینویسیم چون فهمیدنشان از راه امتحانکردن گرانتر است:
- هیچ راهی برای ساخت، فهرستکردن یا ابطال کلید از طریق API نیست. هر دو نوع کلید فقط از پنل ساخته میشوند.
- هیچ راهی برای متوقفکردن یک کمپین نیست.
POST /v1/campaigns/{id}/sendروی میزبان مدیریتی هست؛ مکث، ادامه و لغو نیستند. وقتی بکاند شما ارسالی را زمانبندی کرد، فقط پنل میتواند جلویش را بگیرد. - هیچ راهی برای دانلود خروجی نیست. میتوانید یکی را صف کنید و فهرست کنید. فایل از پنل برداشته میشود.
- هیچ راهی برای تایید یک کمپین یا خواندن اینکه تایید شده یا نه نیست. ثبت درخواست تایید مسیر دارد، خود تصمیم ندارد. یکپارچهسازی جواب را با دوباره امتحانکردن ارسال و خواندن کد
409میفهمد. - جستوجوی وضعیت یک پیام وجود ندارد. مسیری به شکل
GET /v1/messages/{idempotency_key}نیست. - خواندن پرونده وجود ندارد. روی هیچکدام از دو میزبان
GET /v1/profiles/{user_id}نیست. - مسیرهای سناریو، قالب، رضایت، حاکمیت و ممیزی روی سطح عمومی نیستند.
- هیچ
PATCHای در کار نیست.PUT /v1/segments/{id}کل شیء را جایگزین میکند، بدونIf-Matchو بدون هیچ نشانه نسخه، پس دو نویسنده همزمان بیصدا کار همدیگر را پاک میکنند. - هیچ idempotencyای جز روی
POST /v1/messagesنیست. تایماوتی که رویPOST /v1/segmentsدوباره امتحان شود، سگمنت دوم میسازد. - مکانیابی جغرافیایی از روی IP نیست.
countryوregionوcityفقط از چیزی پر میشوند که SDK درcontext.locationمیفرستد. - روی میزبان مدیریتی preflight مربوط به CORS نیست. روی آن mux هیچ پاسخدهنده
OPTIONSای ثبت نشده، پس preflight مرورگر به مسیر پیشفرض میافتد و404میگیرد و خود درخواست اصلا فرستاده نمیشود. مرورگر اصلا نمیتواند API مدیریتی را صدا بزند. همین وضع مطلوب است: کلیدsk_seg_جایش داخل صفحه نیست. - متد اشتباه روی میزبان مدیریتی
405نیست.PUT /v1/campaigns/5به مسیر پیشفرض میافتد و404 unknown_endpointمیگیرد. روی میزبان ورود داده برعکس است: مسیر ثبتنشده زیر/v1/جواب405میگیرد، چون الگوی preflight مربوط به CORS کل آن پیشوند را برای خودش برداشته است.