المطوّرون
واجهة السجل البرمجية
اقرأ سجل PingDX الخاص بك واكتب فيه من برنامج تسجيل آخر أو من سكربت أو من موقعك.
في هذه الصفحة
مقدمة
تتيح واجهة PingDX البرمجية للعضو إبقاء سجل PingDX الخاص به متزامنًا مع برنامج تسجيل آخر أو سكربت أو موقعه الشخصي: تنزيل اتصالات QSO، وإضافة اتصالات جديدة، وتصحيحها أو حذفها. ولا تمنح الوصول إلا إلى سجل العضو الذي أنشأ المفتاح.
عنوان URL الأساسي
https://pingdx.org/api/v1الطلبات والاستجابات بصيغة JSON (UTF-8). أرسل Content-Type: application/json مع كل طلب يحتوي على جسم. التواريخ نصوص بصيغة ISO 8601؛ ويجيب الخادم دائمًا بالتوقيت UTC (2026-10-05T18:42:00.000Z).
يتوفر وصف قابل للقراءة آليًا (OpenAPI 3.1، عام ولا يحتاج إلى مفتاح) لمولّدات الشيفرة والأدوات مثل Postman: https://pingdx.org/api/v1/openapi.json
الحصول على مفتاح
ينشئ كل عضو مفاتيحه بنفسه من الملف الشخصي ← الوصول عبر API. أعطِ كل مفتاح اسمًا (البرنامج أو الموقع الذي يستخدمه) وحدّد صلاحياته:
- قراءة فقط (
READ): يمكنه قراءة السجل (طلباتGET) — وهذا كافٍ لعرض اتصالاتك على موقع ويب. - قراءة وكتابة (
WRITE): يمكنه أيضًا إنشاء اتصالات QSO وتعديلها وحذفها — وهو لازم للمزامنة في الاتجاهين مع برنامج تسجيل. - المفتاح الكامل يظهر مرة واحدة فقط، بعد الإنشاء مباشرة: انسخه فورًا. يحتفظ PingDX ببصمته فقط ولا يمكنه عرضه مرة أخرى.
- يمكنك إلغاء أي مفتاح في أي وقت: يتوقف عن العمل فورًا. الحد الأقصى 10 مفاتيح نشطة لكل عضو.
الأمان
- تعامل مع المفتاح كما تتعامل مع كلمة مرور. لا تضع أبدًا مفتاح WRITE في الشيفرة العامة لموقع ويب (شيفرة JavaScript المرسلة إلى متصفحات الزوار): يستطيع أي شخص قراءته وتعديل سجلك. استدعِ الواجهة من خادمك، أو استخدم مفتاح READ.
- المفتاح يفتح واجهة السجل فقط: ولا يفتح أبدًا رسائلك أو ملفك الشخصي أو كلمة مرورك أو بقية حسابك.
- استخدم مفتاحًا واحدًا لكل برنامج، لتتمكن من إلغاء أحدها دون تعطيل الباقي. تبدأ المفاتيح بالبادئة
pdx_…: يسهل اكتشاف أي مفتاح نُشر بالخطأ — ألغِه عندئذٍ.
المصادقة
أرسل المفتاح مع كل طلب، في الترويسة Authorization (Bearer) أو في الترويسة X-API-Key. جلسة تسجيل الدخول إلى PingDX غير مقبولة في هذه الواجهة، ولا تُستخدم ملفات تعريف الارتباط إطلاقًا.
Authorization: Bearer pdx_…
# أو
X-API-Key: pdx_…بدون مفتاح صالح تجيب الواجهة بالرمز 401؛ ومفتاح READ الذي يحاول الكتابة يحصل على 403:
401 API_KEYS.INVALIDمفتاح مفقود أو غير معروف أو ملغى (أو حساب لم يعد نشطًا).403 API_KEYS.READ_ONLYالمفتاح للقراءة فقط: أنشئ مفتاح WRITE للكتابة.
نقاط النهاية
جميع المسارات نسبية إلى عنوان URL الأساسي. يُعرَّف كل QSO بواسطة clientId، وهو UUID يختاره برنامجك.
GET/me
الصلاحيات المطلوبة: مفتاح READ أو WRITE
العضو الذي يخصّه المفتاح (الإشارة النداء والمحدِّد) وصلاحيات المفتاح. مفيد للتحقق من مفتاح.
الاستجابة
{
"callsign": "14KM001",
"locator": "JN03ql",
"scope": "WRITE"
}الأخطاء
401 API_KEYS.INVALIDمفتاح مفقود أو غير معروف أو ملغى (أو حساب لم يعد نشطًا).429 GENERIC.RATE_LIMITEDطلبات كثيرة جدًا: انتظر قليلًا ثم حاول مجددًا.
GET/logs
الصلاحيات المطلوبة: مفتاح READ أو WRITE
السجل، الأقدم تعديلًا أولًا (مرتّبًا حسب آخر كتابة على الخادم)، صفحةً صفحة.
بدون updatedSince تحصل على السجل كاملًا. ومعه تحصل فقط على اتصالات QSO التي أُنشئت أو عُدّلت أو تأكدت تبادليًا بعد ذلك التوقيت.
المعاملات
updatedSincestring (ISO 8601)استعلاماختياريتاريخ ووقت بصيغة ISO 8601 مع المنطقة الزمنية (مثل
2026-10-01T00:00:00Z). مرّر قيمةserverTimeمن مزامنتك السابقة.limitinteger 1–500 (200)استعلاماختياريعدد اتصالات QSO في الصفحة، من 1 إلى 500. الافتراضي: 200.
cursorstringاستعلاماختياريقيمة
nextCursorمن الصفحة السابقة للحصول على التالية. أبقِupdatedSinceنفسها أثناء التنقل بين الصفحات.
الاستجابة
items: اتصالات QSO في الصفحة (انظر كائن QSO). nextCursor: مرّره كقيمة cursor للحصول على الصفحة التالية؛ وهو null في الصفحة الأخيرة. serverTime: وقت الخادم عند بدء الطلب — وهو قيمة updatedSince في مزامنتك التالية.
{
"items": [
{
"id": "cmgd4w1x70003s60e8k2v9qhz",
"clientId": "0b9e6c1e-5f3a-4d2b-9c7e-1a2b3c4d5e6f",
"band": "ELEVEN_METERS",
"frequencyMhz": "27.555",
"channel": null,
"ctcssTone": null,
"dcsCode": null,
"mode": "USB",
"callsignWorked": "14KM123",
"operatorCallsign": "14KM001",
"dxLocator": "JN18du",
"dxLat": null,
"dxLng": null,
"txPowerW": 4,
"myLocator": "JN03ql",
"splitKhz": null,
"path": null,
"qsoStatus": null,
"rstSent": "59",
"rstReceived": "57",
"qslSent": false,
"qslReceived": false,
"qslVia": null,
"qsoAt": "2026-10-05T18:42:00.000Z",
"notes": null,
"radio": "President Lincoln II+",
"antenna": "Sirio 827",
"crossConfirmedAt": null,
"spottedAt": null,
"createdAt": "2026-10-05T18:43:10.512Z",
"syncedAt": "2026-10-05T18:43:10.512Z"
}
],
"nextCursor": null,
"serverTime": "2026-10-05T18:45:12.345Z"
}الأخطاء
400 VALIDATION.FAILEDطلب غير صالح: معامل خاطئ، أو clientId ليس UUID، أو دفعة بدون مصفوفة entries صالحة (من 1 إلى 200). يصف details المشكلة.401 API_KEYS.INVALIDمفتاح مفقود أو غير معروف أو ملغى (أو حساب لم يعد نشطًا).429 GENERIC.RATE_LIMITEDطلبات كثيرة جدًا: انتظر قليلًا ثم حاول مجددًا.
GET/logs/ids
الصلاحيات المطلوبة: مفتاح READ أو WRITE
معرّفات جميع اتصالات QSO في السجل، دون ترقيم صفحات. قارنها بنسختك لاكتشاف الاتصالات المحذوفة على PingDX (أو من برنامج آخر).
الاستجابة
قد تكون قيمة clientId هي null لبعض اتصالات QSO القديمة جدًا التي أُنشئت قبل وجود المزامنة.
{
"items": [
{ "id": "cmgd4w1x70003s60e8k2v9qhz", "clientId": "0b9e6c1e-5f3a-4d2b-9c7e-1a2b3c4d5e6f" },
{ "id": "cmgd51b2c0007s60efq3m1abc", "clientId": "7d1f2a90-3b4c-4e5f-8a6b-9c0d1e2f3a4b" }
]
}الأخطاء
401 API_KEYS.INVALIDمفتاح مفقود أو غير معروف أو ملغى (أو حساب لم يعد نشطًا).429 GENERIC.RATE_LIMITEDطلبات كثيرة جدًا: انتظر قليلًا ثم حاول مجددًا.
POST/logs/batch
الصلاحيات المطلوبة: مفتاح WRITE
ينشئ أو يحدّث ما يصل إلى 200 من اتصالات QSO في طلب واحد. أي QSO قيمة clientId الخاصة به غير معروفة يُنشأ؛ وأي clientId موجود يحدّث ذلك QSO.
يُتحقَّق من كل QSO على حدة: يُبلَّغ عن QSO غير صالح في نتيجته ولا يمنع أبدًا حفظ البقية. لا يفشل الطلب بأكمله (400) إلا إذا كانت entries مفقودة أو فارغة أو أطول من 200.
المعاملات
entriesQSO[] (1–200)جسممطلوباتصالات QSO المراد إنشاؤها أو تحديثها (انظر كائن QSO).
جسم الطلب
{
"entries": [
{
"clientId": "0b9e6c1e-5f3a-4d2b-9c7e-1a2b3c4d5e6f",
"band": "ELEVEN_METERS",
"frequencyMhz": "27.555",
"mode": "USB",
"callsignWorked": "14KM123",
"qsoAt": "2026-10-05T18:42:00Z",
"rstSent": "59",
"rstReceived": "57",
"dxLocator": "JN18DU"
},
{
"clientId": "7d1f2a90-3b4c-4e5f-8a6b-9c0d1e2f3a4b",
"band": "PMR446",
"channel": 8,
"ctcssTone": "67.0",
"mode": "FM",
"callsignWorked": "PMR-ALPHA",
"qsoAt": "2026-10-05T19:05:00+02:00",
"dxLocator": "JN1"
}
]
}الاستجابة
نتيجة واحدة لكل QSO. id هو معرّف الخادم، أو null إذا رُفض QSO؛ ويذكر error عندئذٍ السبب. تعني permanent: true أن QSO نفسه غير صالح: سيُرفض مجددًا إلى أن يُصحَّح — لا تعِد إرساله كما هو. الرفض بدون permanent مؤقت: حاول لاحقًا.
{
"results": [
{ "clientId": "0b9e6c1e-5f3a-4d2b-9c7e-1a2b3c4d5e6f", "id": "cmgd4w1x70003s60e8k2v9qhz", "spottedAt": null },
{ "clientId": "7d1f2a90-3b4c-4e5f-8a6b-9c0d1e2f3a4b", "id": null, "error": "LOCATOR_INVALID", "permanent": true }
]
}الأخطاء
400 VALIDATION.FAILEDطلب غير صالح: معامل خاطئ، أو clientId ليس UUID، أو دفعة بدون مصفوفة entries صالحة (من 1 إلى 200). يصف details المشكلة.401 API_KEYS.INVALIDمفتاح مفقود أو غير معروف أو ملغى (أو حساب لم يعد نشطًا).403 API_KEYS.READ_ONLYالمفتاح للقراءة فقط: أنشئ مفتاح WRITE للكتابة.429 GENERIC.RATE_LIMITEDطلبات كثيرة جدًا: انتظر قليلًا ثم حاول مجددًا.
DELETE/logs/{clientId}
الصلاحيات المطلوبة: مفتاح WRITE
يحذف QSO من سجلك بواسطة clientId. وإذا كان قد أُرسل إلى كتلة DX (DX cluster) فإن السبوت يُزال أيضًا.
المعاملات
clientIdstring (UUID)مسارمطلوبقيمة
clientIdالخاصة بـ QSO (وهي UUID).
الاستجابة
بلا جسم (204). أرسل هذا الطلب بدون جسم وبدون Content-Type.
HTTP/1.1 204 No Contentالأخطاء
400 VALIDATION.FAILEDطلب غير صالح: معامل خاطئ، أو clientId ليس UUID، أو دفعة بدون مصفوفة entries صالحة (من 1 إلى 200). يصف details المشكلة.401 API_KEYS.INVALIDمفتاح مفقود أو غير معروف أو ملغى (أو حساب لم يعد نشطًا).403 API_KEYS.READ_ONLYالمفتاح للقراءة فقط: أنشئ مفتاح WRITE للكتابة.404 GENERIC.NOT_FOUNDلا يوجد QSO بهذا clientId في سجلك.429 GENERIC.RATE_LIMITEDطلبات كثيرة جدًا: انتظر قليلًا ثم حاول مجددًا.
GET/openapi.json
الصلاحيات المطلوبة: لا شيء (عام)
وصف OpenAPI 3.1 لهذه الواجهة (JSON). عام: لا يحتاج إلى مفتاح.
كائن QSO
يُرسَل الكائن نفسه إلى POST /logs/batch ويعيده GET /logs. يحدّد الحقل band أحد نوعين:
- 11 m (CB) —
"band": "ELEVEN_METERS":frequencyMhz(اختياري)؛ ويجب أن تكونchannelوctcssToneوdcsCodeغائبة أوnull. - PMR446 —
"band": "PMR446":channel(من 1 إلى 16) مطلوبة، وctcssTone/dcsCodeاختياريتان؛ ويجب أن تكونfrequencyMhzغائبة أوnull. - التحديث يستبدل QSO: أرسل دائمًا QSO كاملًا. أي حقل محذوف يُعاد ضبطه (فارغ، أو
falseللحقلينqslSent/qslReceived)، باستثناءoperatorCallsignوdxLatوdxLngوtxPowerWوradioوantenna، التي تحتفظ بقيمتها المخزّنة عند حذفها. - تُقتطع المسافات الزائدة من النصوص، وتُوحَّد صيغة المحدِّدات (
jn18DU←JN18du) وتُتجاهل الحقول غير المعروفة. الحقول للقراءة فقط يضبطها الخادم: لا حاجة إلى إرسالها.
clientIdمطلوبstring (UUID)معرّفك الثابت لهذا QSO: UUID عشوائي (مثل
crypto.randomUUID())، يُولَّد مرة واحدة ويُخزَّن مع QSO في برنامجك. وهو مفتاح المزامنة.bandمطلوب"ELEVEN_METERS" | "PMR446"النطاق:
ELEVEN_METERS(11 m / CB) أوPMR446.qsoAtمطلوبstring (ISO 8601)تاريخ QSO ووقته، بصيغة ISO 8601 مع المنطقة الزمنية (
Zأو+02:00). يُخزَّن ويُعاد بالتوقيت UTC.frequencyMhzاختياريstring "27.555" · 26.000–28.000للنطاق 11 m فقط: التردد بالميغاهرتز، نصًّا بثلاث خانات عشرية بالضبط، بين 26.000 و28.000.
channelمطلوب في PMR446integer 1–16لـ PMR446 فقط (ومطلوب فيه): القناة، من 1 إلى 16.
ctcssToneاختياريstring "67.0" · ^\d{2,3}\.\d$لـ PMR446 فقط: نغمة CTCSS بالهرتز، بخانة عشرية واحدة (
67.0،103.5).dcsCodeاختياريstring "D023N" · ^D?[0-7]{3}[NI]?$لـ PMR446 فقط: رمز DCS، 3 أرقام ثمانية مع بادئة اختيارية
DوقطبيةN/I(D023N).modeاختياري"FM" | "AM" | "SSB" | "USB" | "LSB" | "CW" | "DIGITAL"نمط الإرسال.
callsignWorkedاختياريstring ≤ 20الإشارة النداء للمحطة التي جرى الاتصال بها.
operatorCallsignاختياريstring ≤ 20إشارتك النداء لهذا QSO (مفيدة إن كنت تستخدم عدة إشارات). يُحتفَظ بها عند حذفها في تحديث.
dxLocatorاختياريstring "JN18" | "JN18du"محدِّد Maidenhead للمحطة المتصل بها، من 4 أو 6 محارف.
dxLatاختياريnumber −90…90خط العرض الدقيق للمحطة المتصل بها، إن كان معروفًا. تفضّله الخرائط على المحدِّد.
dxLngاختياريnumber −180…180خط الطول الدقيق للمحطة المتصل بها، إن كان معروفًا.
txPowerWاختياريinteger 0–100000قدرة الإرسال لديك بالواط الصحيح.
myLocatorاختياريstring "JN03" | "JN03ql"محدِّدك في هذا QSO (مثلًا عند الإرسال من موقع متنقل)، من 4 أو 6 محارف.
splitKhzاختياريinteger −99999…99999الانقسام (split) بالكيلوهرتز (الفرق بين تردد الإرسال وتردد الاستقبال).
pathاختياري"SP" | "LP"مسار الانتشار: المسار القصير (
SP) أو المسار الطويل (LP).qsoStatusاختياري"HRD" | "WKD" | "CFM"حالة الاتصال: مسموع (
HRD)، متصل به (WKD)، مؤكَّد (CFM).rstSentاختياريstring ≤ 10التقرير المرسَل (مثل
59).rstReceivedاختياريstring ≤ 10التقرير المستلَم.
qslSentاختياريboolean (false)بطاقة QSL مرسَلة. الافتراضي:
false.qslReceivedاختياريboolean (false)بطاقة QSL مستلَمة. الافتراضي:
false.qslViaاختياري"DIRECT" | "BUREAU" | "EQSL" | "ECARD"وسيلة تبادل QSL:
DIRECT(بريد)،BUREAU،EQSL(eQSL.cc) أوECARD(بطاقة إلكترونية أخرى).notesاختياريstring ≤ 2000ملاحظات حرة.
radioاختياريstring ≤ 80جهاز الراديو المستخدم (نص حر).
antennaاختياريstring ≤ 80الهوائي المستخدم (نص حر).
sendSpotاختياريbooleanللكتابة فقط ولا يُعاد أبدًا: القيمة
trueتنشر QSO أيضًا كسبوت على كتلة DX الخاصة بـ PingDX (مرة واحدة لكل QSO). للاتصالات المباشرة فقط، وليس لاستيراد الاتصالات القديمة.idللقراءة فقطstringمعرّف الخادم الخاص بـ QSO.
crossConfirmedAtللقراءة فقطstring (ISO 8601) | nullيضبطه PingDX عندما تسجّل المحطة الأخرى QSO نفسه على PingDX (التأكيد المتبادل).
spottedAtللقراءة فقطstring (ISO 8601) | nullوقت نشر QSO كسبوت في الكتلة، أو
null.createdAtللقراءة فقطstring (ISO 8601)وقت أول حفظ لـ QSO على PingDX.
syncedAtللقراءة فقطstring (ISO 8601)آخر كتابة على الخادم لـ QSO (وبه يُرتَّب
GET /logs).
دليل المزامنة
مزامنة متينة في الاتجاهين بين برنامجك وPingDX في ست خطوات:
- 1
التنزيل الكامل الأول
استدعِ
GET /logs?limit=500بدونupdatedSince، ثم تابعnextCursorحتى يصبحnull. احتفظ بقيمةserverTimeمن الصفحة الأولى. - 2
التنزيل التزايدي
في المرة التالية استدعِ
GET /logs?updatedSince=…بقيمةserverTimeالمحفوظة، وتابع الصفحات بالطريقة نفسها، ثم احفظ قيمةserverTimeالجديدة (من الصفحة الأولى أيضًا) فقط بعد معالجة جميع الصفحات. استلام QSO مرتين لا ضرر فيه: طبّقه بحسبclientId. - 3
إرسال اتصالاتك
امنح كل QSO في برنامجك قيمة
clientIdمن نوع UUID مرة واحدة وإلى الأبد، وخزّنها. أرسل اتصالات QSO الجديدة أو المعدّلة بواسطةPOST /logs/batch، بحد أقصى 200 في الطلب. إعادة إرسال QSO نفسه آمنة: يُحدَّث بدل أن يُنشأ نسخة مكررة. - 4
اتصالات QSO المرفوضة
اقرأ كل نتيجة: إذا كانت
idتساويnullمعpermanent: true، فاعرض QSO على المستخدم لتصحيحه (يوضّحerrorالسبب) بدل إعادة المحاولة. وبدونpermanentأعد المحاولة لاحقًا. - 5
عمليات الحذف
للحذف على PingDX:
DELETE /logs/{clientId}. ولاكتشاف اتصالات QSO المحذوفة على PingDX:GET /logs/ids، ثم احذف من نسختك كل QSO لم تعد قيمةclientIdالخاصة به مدرجة. - 6
التعارضات
آخر كتابة هي التي تسود، لكل
clientId: لا يوجد دمج حقلًا بحقل. يلتقط تطبيق PingDX اتصالات QSO المكتوبة عبر الواجهة البرمجية عند مزامنته التالية (عند فتحه، أو خلال بضع ثوانٍ عند الاتصال بالإنترنت).
الأخطاء والحدود
تستخدم الأخطاء رمز حالة HTTP وجسم JSON برمز ثابت (وليس نصًّا مترجمًا أبدًا):
{ "error": { "code": "API_KEYS.READ_ONLY" } }400 VALIDATION.FAILEDطلب غير صالح: معامل خاطئ، أو clientId ليس UUID، أو دفعة بدون مصفوفة entries صالحة (من 1 إلى 200). يصف details المشكلة.
401 API_KEYS.INVALIDمفتاح مفقود أو غير معروف أو ملغى (أو حساب لم يعد نشطًا).
403 API_KEYS.READ_ONLYالمفتاح للقراءة فقط: أنشئ مفتاح WRITE للكتابة.
404 GENERIC.NOT_FOUNDلا يوجد QSO بهذا clientId في سجلك.
429 GENERIC.RATE_LIMITEDطلبات كثيرة جدًا: انتظر قليلًا ثم حاول مجددًا.
500 GENERIC.INTERNAL_ERRORخطأ في الخادم: حاول لاحقًا.
أسباب الرفض لكل QSO
في POST /logs/batch، يحمل QSO المرفوض id: null ورمز error:
CLIENT_ID_INVALIDclientId ليس UUID صالحًا.DATETIME_INVALIDqsoAt ليس تاريخًا ووقتًا بصيغة ISO 8601 مع المنطقة الزمنية.LOCATOR_INVALIDdxLocator أو myLocator ليس محدِّد Maidenhead من 4 أو 6 محارف.FREQUENCY_FORMAT_INVALIDيجب أن تكون frequencyMhz نصًّا بثلاث خانات عشرية (27.555).FREQUENCY_OUT_OF_BAND_11MfrequencyMhz خارج النطاق 26.000–28.000 MHz.CHANNEL_OUT_OF_RANGEقناة PMR446 خارج المدى 1–16.CTCSS_FORMAT_INVALIDيجب أن تشبه ctcssTone القيمة 67.0.DCS_FORMAT_INVALIDيجب أن تشبه dcsCode القيمة D023N.CLIENT_ID_CONFLICTهذا clientId يخص بالفعل QSO لعضو آخر: ولّد UUID جديدًا.
أما المخالفات الأخرى للمخطط (حقل مفقود، قيمة غير معروفة في enum، نص طويل جدًا…) فتعيد رسالة المدقِّق بالإنجليزية، مثل Invalid input: expected string, received undefined، مع permanent: true أيضًا.
الحدود
- 300 طلبًا في الدقيقة لكل عنوان IP، وما زاد عن ذلك يُجاب عنه بالرمز
429(وتبيّن الترويسةRetry-Afterمدة الانتظار). جمّع اتصالات QSO في دفعات بدل إرسالها واحدًا واحدًا. - 200 من اتصالات QSO كحد أقصى لكل
POST /logs/batch. - من 1 إلى 500 QSO في كل صفحة من
GET /logs(200 افتراضيًا). - 10 مفاتيح نشطة كحد أقصى لكل عضو.
الاستدعاء من متصفح (CORS)
تقبل /api/v1 الطلبات من أي أصل (CORS)، بدون ملفات تعريف الارتباط أو بيانات الاعتماد. لذلك يمكن لصفحة ويب استدعاؤها مباشرة — ولكن بمفتاح READ فقط، لأن شيفرة الصفحة العامة مرئية للجميع.
أمثلة
استبدل pdx_… بمفتاحك. هذه الأمثلة جاهزة للنسخ؛ والتعليقات فقط بالإنجليزية.
curl
# Who am I? (checks the key)
curl -H "Authorization: Bearer pdx_…" https://pingdx.org/api/v1/me
# Everything changed since my last sync
curl -H "Authorization: Bearer pdx_…" \
"https://pingdx.org/api/v1/logs?updatedSince=2026-10-01T00:00:00Z&limit=500"
# Create or update a QSO (WRITE key)
curl -X POST -H "Authorization: Bearer pdx_…" \
-H "Content-Type: application/json" \
https://pingdx.org/api/v1/logs/batch \
-d '{"entries":[{"clientId":"0b9e6c1e-5f3a-4d2b-9c7e-1a2b3c4d5e6f",
"band":"ELEVEN_METERS","frequencyMhz":"27.555","mode":"USB",
"callsignWorked":"14KM123","qsoAt":"2026-10-05T18:42:00Z",
"rstSent":"59","rstReceived":"57"}]}'
# Delete it (no body, no Content-Type)
curl -X DELETE -H "Authorization: Bearer pdx_…" \
https://pingdx.org/api/v1/logs/0b9e6c1e-5f3a-4d2b-9c7e-1a2b3c4d5e6fJavaScript (fetch)
تعمل في Node.js 18+ وDeno. أبقِ المفتاح في جهة الخادم (متغير بيئة).
const BASE = "https://pingdx.org/api/v1";
const KEY = process.env.PINGDX_API_KEY; // pdx_… — keep it server-side
async function call(method, path, body) {
const res = await fetch(BASE + path, {
method,
headers: {
Authorization: `Bearer ${KEY}`,
...(body ? { "Content-Type": "application/json" } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
if (res.status === 204) return null;
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error?.code}`);
return data;
}
// Full download, page by page
async function pullAll(updatedSince) {
const qsos = [];
let cursor = null;
let since = null;
do {
const q = new URLSearchParams({ limit: "500" });
if (updatedSince) q.set("updatedSince", updatedSince);
if (cursor) q.set("cursor", cursor);
const page = await call("GET", `/logs?${q}`);
since ??= page.serverTime; // keep the FIRST page's serverTime
qsos.push(...page.items);
cursor = page.nextCursor;
} while (cursor);
return { qsos, nextUpdatedSince: since };
}
// Push local QSOs (create or update by clientId)
const { results } = await call("POST", "/logs/batch", {
entries: [{
clientId: crypto.randomUUID(), // store it with your QSO, reuse it forever
band: "PMR446", channel: 8, ctcssTone: "67.0", mode: "FM",
callsignWorked: "PMR-ALPHA", qsoAt: new Date().toISOString(),
}],
});
for (const r of results) if (r.id === null) console.warn(r.clientId, r.error, r.permanent);Python (requests)
import os, uuid, requests
BASE = "https://pingdx.org/api/v1"
S = requests.Session()
S.headers["Authorization"] = f"Bearer {os.environ['PINGDX_API_KEY']}" # pdx_…
def pull(updated_since=None):
"""Every QSO changed since updated_since (all of them if None)."""
qsos, cursor, next_since = [], None, None
while True:
params = {"limit": 500}
if updated_since:
params["updatedSince"] = updated_since
if cursor:
params["cursor"] = cursor
r = S.get(f"{BASE}/logs", params=params, timeout=30)
r.raise_for_status()
page = r.json()
next_since = next_since or page["serverTime"] # first page's serverTime
qsos += page["items"]
cursor = page["nextCursor"]
if not cursor:
return qsos, next_since
def push(entries):
"""Create or update up to 200 QSOs; returns the rejected ones."""
r = S.post(f"{BASE}/logs/batch", json={"entries": entries}, timeout=30)
r.raise_for_status()
return [x for x in r.json()["results"] if x["id"] is None]
rejected = push([{
"clientId": str(uuid.uuid4()), # keep it with your QSO
"band": "ELEVEN_METERS", "frequencyMhz": "27.555", "mode": "USB",
"callsignWorked": "14KM123", "qsoAt": "2026-10-05T18:42:00Z",
}])
# Deleted on PingDX? Compare clientIds with your copy
server_ids = {x["clientId"] for x in S.get(f"{BASE}/logs/ids", timeout=30).json()["items"]}هل لديك سؤال أو برنامج تودّ ربطه؟ راسلنا من صفحة الاتصال.