المبرمجين

مرحبًا بك في وثائق المطور الخاصة بمنصة InCountry. لقد جمعنا جميع الموارد المفيدة في مكان واحد لكي تبدأ على الفور في InCountry Ecosystem.

تشتمل مجموعة أدوات مطور InCountry على SDK وواجهة برمجة تطبيقات وخادم وكيل وخط من عمليات الدمج الجاهزة. تغطي حلول الامتثال الشاملة لدينا أكثر من 90 دولة ، مما يسمح لك بتوسيع SaaS والتطبيقات الداخلية لتغطية جميع احتياجات موقع البيانات.

منصة InCountry

diagram-developers

أدوات InCountry

توفر منصة InCountry مجموعة متنوعة من المنتجات لخدمات موقع البيانات ، على النحو التالي:

بوابة InCountry

.

InCountry Portal هو نقطة أولية للبدء في منصة InCountry. يوفر خيار التسجيل لإنشاء حساب مؤسسة ، والمزيد من التحكم في مخازن البيانات الخاصة بك. هنا يمكنك أيضًا إدارة حسابات الموظفين الآخرين داخل مؤسستك الذين يمكنهم الوصول إلى مخازن البيانات هذه وإجراء عمليات محددة

حدود InCountry

.

InCountry Border هو مكون اتصال بيانات يتكامل بين أجزاء الواجهة الأمامية والخلفية لتطبيقك. يقوم بتوكيل الطلبات بالبيانات المنظمة من الواجهة الأمامية إلى الواجهة الخلفية ويكتبها إلى منصة InCountry أثناء إعادة البيانات المميزة إلى الواجهة الخلفية. يسمح لك بتنقيح البيانات وإلغاء تعديلها أثناء التنقل بأقل جهد ممكن.

واجهة برمجة تطبيقات InCountry REST

.

تعد واجهة برمجة تطبيقات InCountry REST مجموعة من الأساليب RESTful التي تتيح لك الاستعلام عن البيانات المنظمة في منصة InCountry بدون دمج InCountry SDK في تطبيقك. إنه يبسط اتصال البيانات ويتيح لك إنشاء تطبيقات بسرعة مع إمكانات توطين البيانات.

InCountry SDK

.

InCountry SDK عبارة عن مجموعة من أدوات تطوير البرامج للغات البرمجة التالية:

  • Python
  • Java
  • Node.js
  • .Net (قريبًا)

يمكنك دمج InCountry SDK في تطبيقك أو نظامك لتطوير سيناريوهات معالجة البيانات المعقدة واستخدام جميع مزايا InCountry Platform لترجمة البيانات. باستخدام InCountry SDK ، يمكنك تنفيذ عمليات تكامل شاملة ومعالجة البيانات المنظمة بالطريقة التي تريدها. بالإضافة إلى ذلك ، باستخدام InCountry SDK ، يمكنك إدارة مفاتيح التشفير بنفسك واستخدام طرق تشفير مخصصة إذا لزم الأمر.

تكاملات SaaS (بما في ذلك Salesforce)

.

يوفر نظام InCountry Platform مجموعة متنوعة من عمليات التكامل الأصلية مع حلول SaaS الأفضل أداءً ، بما في ذلك Salesforce و ServiceNow. يمكنك استخدام عمليات التكامل الجاهزة للتعامل مع البيانات المنظمة من تطبيقات SaaS. استفد من عمليات الدمج والحزم الحالية ، وخصصها وفقًا لبياناتك واحتياجاتك.

من خلال تكامل InCountry for Salesforce ، يمكنك تنقيح البيانات المنظمة في Salesforce عند الوصول إليها خارج البلد الأصلي ، أو تقييد الوصول إليها للمستخدمين من بلدان معينة ، أو نسخها إلى بلدان أخرى دون انتهاك لوائح الامتثال المحلية.

ميزات منتجات InCountry

يحتوي القسم التالي على قائمة الميزات التي توفرها مجموعة أدوات InCountry:

بوابة InCountry

.
  • التسجيل الأولي للمستخدمين الجدد واستعادة الحساب
  • إدارة المنظمة وأعضائها
  • إدارة الوصول إلى حساب المؤسسة
  • إنشاء بيئات التخزين والوصول إلى أوراق الاعتماد
  • وحدة توصية لتخزين البيانات المنظمة لكل بلد

حدود InCountry

.
  • تكامل سلس بين الواجهة الأمامية والخلفية لتطبيقك
  • سياسات التنقيح وعدم التمكين لأنواع البيانات المنظمة
  • تنقيح / عدم إعادة تفكيك البيانات على الفور
  • التحكم في البيانات المستندة إلى المستخدم
  • ترميز البيانات المنظمة مع الحفاظ على تنسيقات البيانات
  • تشفير البيانات المنظمة في منصة InCountry

واجهة برمجة تطبيقات InCountry REST

.
  • إنشاء نموذج أولي للتطبيق بسرعة
  • جميع عمليات CRUD في متناول اليد
  • تشفير البيانات المنظمة في منصة InCountry
  • استرداد البيانات عند الطلب لاقتصاد النطاق الترددي للبيانات
  • وظائف بدون خادم لمعالجة البيانات والتحقق منها وتجميعها

InCountry SDK

.
  • التوفر للغات البرمجة الثلاث
  • تنفيذ سيناريوهات البيانات المعقدة داخل التطبيق الخاص بك
  • تشفير البيانات بالطريقة التي تحتاجها
  • أداء قوي وسرعة الوصول إلى البيانات
  • ترحيل البيانات وتناوب المفاتيح
  • طرق التشفير المخصصة
  • كتابة / قراءة البيانات على دفعات
  • تأمين اتصال البيانات مع TLS 1.2.

تكاملات SaaS (بما في ذلك Salesforce)

.
  • التكامل الفوري مع تطبيقات SaaS ، بما في ذلك Salesforce
  • الحد الأدنى من الوقت للامتثال لموقع البيانات
  • وظائف بدون خادم مع JavaScript
  • تشفير البيانات
  • أوضاع معالجة البيانات المتعددة: التنقيح والتقييد والنسخ المتماثل

اختيار الحل الأفضل لاحتياجاتك

بناء تطبيق جديد

.

لإنشاء تطبيق جديد ، أولاً وقبل كل شيء ، حدد مستوى التكامل وتدفق اتصال البيانات بين تطبيقك ومنصة InCountry. بالإضافة إلى ذلك ، حدد أيضًا نموذج تنظيم البيانات الذي تريد استخدامه والدول التي سيخدمها تطبيقك.

بمجرد تحديد كل هذه النقاط ، يمكنك المتابعة لاختيار أداة InCountry ، على النحو التالي:

1. إذا كان التطبيق الخاص بك يحتوي على واجهة أمامية وخلفية فقط مع تدفق بيانات اتصال بيانات بسيط a135792 ، فإن الحل الأسهل بالنسبة لك هو تكامله مع InCountry Border. سيكون عليك فقط إعادة توجيه طلبات البيانات الخاصة بك من الواجهة الأمامية إلى وكيل InCountry وتوجيهها إلى الواجهة الخلفية.

2. إذا كنت ترغب في تنفيذ تطبيق أكثر استقلالية ، فأنت بحاجة إلى استخدام واجهة برمجة تطبيقات InCountry REST. سيتعين عليك تفويض طلبات البيانات بالشهادة الصادرة من جانب العميل في طلبك.

3. إذا كنت بحاجة إلى إنشاء تطبيق بمنطق عمل شامل ، وتوطين البيانات في بلدان متعددة ، والمزيد من تجميع البيانات منها ، فأنت بحاجة إلى اختيار InCountry SDK. يأتي في مجموعة متنوعة من لغات البرمجة بما في ذلك Python و Java و Node.js. يوفر جميع أدوات التطوير الأساسية لتنفيذ حلول قوية وشاملة للتعامل مع البيانات المنظمة وتشفيرها وجلبها عند الطلب. باستخدام InCountry SDK ، يمكنك الحصول على المزيد من الإمكانات لإدارة البيانات ومعالجتها وتخزينها بالطريقة التي تريدها.

بالنسبة للتطبيقات الداخلية المخصصة والمبنية حديثًا ، يرجى مراجعة SDK الخاص بنا.

دمج موقع البيانات في التطبيق الحالي

.

لدمج موقع البيانات في تطبيقك الحالي ، استخدم InCountry Border. يوفر طريقة سريعة للتكامل في تدفق اتصال البيانات بين الواجهة الأمامية والخلفية. ستحتاج إلى إعادة توجيه طلبات البيانات من الواجهة الأمامية للتطبيق إلى InCountry Border والتي ستنفذ تنقيح البيانات وإلغاء إعادة فكها. بعد إجراء معالجة البيانات هذه ، ستعمل InCountry Border على وكيل إضافي لطلب البيانات بالبيانات المنظمة والرموز إلى الواجهة الخلفية لتطبيقك.

يمكن لهذا المنتج إجراء تنقيح سريع للبيانات و “إلغاء إعادة التفكيك” ، بما في ذلك تشفير حقول البيانات المنظمة وترميزها للحفاظ على أنماط تنسيق البيانات الحالية المحددة في الواجهة الخلفية لتطبيقك. يتطلب هذا الحد الأدنى من التعديلات داخل تطبيقك ولا توجد تغييرات على الإطلاق في نموذج البيانات الخاص بك.

تكامل أنظمة الضرب

.

لدمج أنظمة متعددة ومعالجة البيانات المنظمة بينها من خلال منصة InCountry ، استخدم InCountry REST API. بهذه الطريقة يمكنك كتابة البيانات المنظمة من نظام واحد إلى منصة InCountry ، وقراءتها من النظام الآخر عن طريق تنفيذ الطلبات المشتركة من خلال InCountry REST API. سيتم تنفيذ عملية ترميز البيانات الخاضعة للتنظيم بواسطة منصة InCountry ، لذلك لا تحتاج إلى تعديل أي شيء على أنظمتك باستثناء تخزين مفاتيح التسجيل للتمييز بين السجلات.

بنفس الطريقة ، يمكنك دمج المزيد من الأنظمة ، على سبيل المثال ، يمكن أن يكون لديك نظام CRM ونظام ERP يخزن طبقات البيانات الخاصة بهما التي تريد دمجها وتحليلها في نظام تجميع البيانات الخاص بك. ستتيح لك واجهة برمجة تطبيقات InCountry REST القيام بذلك مع الحد الأدنى من التعديلات في تدفق اتصالات البيانات.

يمكن لـ REST API دمج أنظمة متعددة وتنفيذ استعلامات البيانات مع كل من البيانات المنظمة وغير المنظمة بين الأنظمة. بهذه الطريقة يمكنك دمج البيانات لعرضها داخل أنظمتك.

إنشاء نسخة احتياطية للبيانات

.

لإنشاء نسخة احتياطية للبيانات من السجلات مع البيانات المنظمة ، استخدم InCountry REST API. يسمح لك بإجراء استيراد مجمع من السجلات في منصة InCountry. يمكنك تخزين هذه السجلات لفترة طويلة من خلال الوصول إليها عند الطلب عند الحاجة.

موقع البيانات لحلول SaaS

.

إذا كنت تستخدم أي حل SaaS ، على سبيل المثال Salesforce ، فاستخدم حلولنا الأصلية للتكامل SaaS لإقامة البيانات والاحتفاظ الآمن بالبيانات في InCountry Platform. يعد النشر السريع والحد الأدنى من الإعداد من المزايا الرئيسية للحلول الجاهزة للاستخدام. تتيح لك الوظائف التي لا تحتوي على خادم تحسين وتنويع عمليات اتصال البيانات لبياناتك المنظمة.

في الوقت الحالي ، توفر منصة InCountry عمليات التكامل الجاهزة التالية:

  • قوة المبيعات
  • الخدمة الآن
  • مامبو
  • قطعة
  • تويليو

نماذج لوائح البيانات

يمكن أن تؤثر قواعد توطين البيانات على موقع تخزين البيانات المنظمة وعرضها ومعالجتها والتي يتم وضعها في الخريطة المجمعة في نماذج InCountry الثلاثة هذه

نماذج حماية البيانات في البلدان
.منسوخمحددمخفضة
تخزينداخل وخارج البلادفي البلدفي البلد
المعاينةداخل وخارج البلادداخل وخارج البلادفي البلد
معالجةداخل وخارج البلادخارج البلادفي البلد

تشتمل مجموعة أدوات مطور InCountry على SDK وواجهة برمجة تطبيقات وخادم وكيل وخط من عمليات الدمج الجاهزة. تغطي حلول الامتثال الشاملة لدينا أكثر من 90 دولة ، مما يسمح لك بتوسيع SaaS والتطبيقات الداخلية لتغطية جميع احتياجات موقع البيانات.

نموذجوصف

منسوخ

يتم حفظ البيانات الخاضعة للتنظيم أولاً في بلد المنشأ ، ثم يمكن نسخ البيانات إلى بلدان أخرى. في هذه الحالة ، يتم حفظ البيانات المنظمة في نظام InCountry Platform الذي يقع في البلد الأصلي ، ثم يتم نسخها إلى نظامك أو مخزن البيانات في بلد آخر.

محدد

يتم تخزين البيانات المنظمة داخل منصة InCountry في البلد الأصلي ، ولكن يمكن تركها في بعض الحالات عند قراءتها.

يتم تنفيذ جميع طلبات البيانات الخاصة بالبيانات المنظمة مباشرة إلى InCountry Platform داخل البلد الأصلي عندما تطلب هذه البيانات خارج البلد الأصلي. لا يتم حفظ البيانات المنظمة في نظامك.

مخفضة

يتم تخزين البيانات المنظمة داخل البلد الأصلي ولا يمكن تركها. يتم تنظيم الوصول إلى هذه البيانات اعتمادًا على موقع المستخدم الحالي ، لذلك سيرى المستخدم النهائي إما البيانات المنظمة (إذا كان بلد الوصول يطابق البلد الأصلي للبيانات) أو البيانات المنقحة (إذا كان بلد الوصول يختلف عن البلد الأصلي للبيانات) .

يتم تنفيذ جميع طلبات البيانات الخاصة بالبيانات المنظمة مباشرة إلى منصة InCountry داخل البلد الأصلي. لا يتم حفظ البيانات المنظمة في نظامك.

الخطوات التالية

يمكنك العثور أدناه على روابط نماذج البرامج الجاهزة للاستخدام التي يمكنك استخدامها لإنشاء التطبيقات بنفسك.

1.
تفويض

توفر منصة InCountry طرقًا مختلفة لتفويض طلبات البيانات اعتمادًا على المنتج الذي تستخدمه.

2.
تفويض OAuth 2.0

تستخدم منصة InCountry بروتوكول OAuth 2.0 للمصادقة والتفويض. تدعم منصة InCountry سيناريوهات OAuth 2.0 الشائعة مثل تلك الخاصة بخادم الويب ومن جانب العميل. تضمن آلية مصادقة OAuth 2.0 أمان حسابك وتوفر تحكمًا كاملاً في جلسات المستخدم الخاصة بك.

لبدء استخدام منصة InCountry ، أنشئ حسابًا جديدًا على بوابة InCountry. هنا تحتاج إلى إنشاء بيئة جديدة والعميل. لكل عميل ، ستحصل على معرف العميل وسر العميل الذي يمكنك استخدامه مرة أخرى للحصول على رمز مميز من منصة InCountry.

3.
الحصول على معرف العميل وسر العميل

1. قم بالتسجيل أو تسجيل الدخول إلى بوابة InCountry.

2. خلق بيئة جديدة.

3. إنشاء عميل جديد.

4. ستصدر بوابة InCountry معرّف العميل وسر العميل للبيئة.

5. استخدم العميل وسر العميل الذي تم إصداره لإنشاء رمز مميز باستخدام SDK.

4.
تفويض بشهادة

تستخدم واجهة برمجة تطبيقات InCountry REST شهادة لتفويض طلبات البيانات إلى منصة InCountry. سيقدم لك فريق InCountry الشهادة بناءً على طلبك.

وصفات سريعة

يمكنك العثور أدناه على قائمة وصفات التعليمات البرمجية السريعة حول كيفية تنفيذ هذا الحل أو ذاك باستخدام منصة InCountry.

create-storage

إنشاء مثيل تخزين

.

لبدء استخدام InCountry Platform ، تحتاج إلى إنشاء مثيل تخزين:

  • Python SDK
  • Node.js SDK
  • Java SDK
Python SDK

To access your data in InCountry Platform with Python SDK, you need to create an instance of the Storage class.

class Storage:
    def __init__(
        self,
        environment_id: Optional[str] = None,   # Required to be passed in, or as the INC_API_KEY environment variable
        api_key: Optional[str] = None,          # Required when using API key authorization, or as an environment variable
        client_id: Optional[str] = None,        # Required when using oAuth authorization, can be also set through the INC_CLIENT_ID variable
        client_secret: Optional[str] = None,    # Required when using oAuth authorization, can be also set through the INC_CLIENT_SECRET variable
        endpoint: Optional[str] = None,         # Optional. Defines API URL. Can also be set up through the INC_ENDPOINT environment variable
        encrypt: Optional[bool] = True,         # Optional. If False, encryption is not applied
        debug: Optional[bool] = False,          # Optional. If True enables the additional debug logging
        options: Optional[Dict[str, Any]] = {}, # Optional. It is used to fine-tune some configurations
        custom_encryption_configs: Optional[List[dict]] = None, # Optional. List of custom encryption configurations
        secret_key_accessor: Optional[SecretKeyAccessor] = None, # Instance of the SecretKeyAccessor class. It is used to fetch the encryption secret
    ):
        ...

WARNING

API Key authorization is being deprecated. The backward compatibility is preserved for the api_key parameter but you no longer can access API keys (neither old nor new) from your dashboard.


The client_id, client_secret, and environment_id variables can be fetched from your dashboard on Incountry Portal.

The endpoint variable defines API URL and is used to override the default one.

You can disable the encryption (not recommended). Set the encrypt property to false if you want to disable it. Avoid using it when using for production data.

The options variable allows you to tweak some SDK configurations

{
    "http_options": {
        "timeout": int,         # In seconds. Should be greater than 0
    },
    "auth_endpoints": dict,     # A custom endpoints regional map that should be used for fetching oAuth tokens

    "countries_endpoint": str,  # If your PoPAPI configuration relies on a custom PoPAPI server
                                # (rather than the default one) use the `countriesEndpoint` option
                                # to specify the endpoint responsible for fetching the list of supported countries

    "endpoint_mask": str,       # Defines API base hostname part to use.
                                # If set, all requests will be sent to https://${country}${endpointMask} host
                                # instead of the default one (https://${country}-mt-01.api.incountry.io)
}

Below you can find the example of how to create a storage instance

from incountry import Storage, SecretKeyAccessor

storage = Storage(
    api_key="<api_key>",
    environment_id="<env_id>",
    debug=True,
    secret_key_accessor=SecretKeyAccessor(lambda: "password"),
    options={
        "http_options": {
            "timeout": 5
        },
        "countries_endpoint": "https://private-pop.incountry.io/countries",
        "endpoint_mask" ".private-pop.incountry.io",
    }
)

oAuth Authentication

The SDK also supports oAuth authentication credentials instead of plain API key authorization. oAuth authentication flow is mutually exclusive with API key authentication - you need to provide either an API key or oAuth credentials.

Below you can find the example of how to create a storage instance with oAuth credentials (and also provide a custom oAuth endpoint):

from incountry import Storage, SecretKeyAccessor

storage = Storage(
    client_id="<client_id>",
    client_secret="<client_secret>",
    environment_id="<env_id>",
    debug=True,
    secret_key_accessor=SecretKeyAccessor(lambda: "password"),
    options={
        "auth_endpoints": {
            "default": "https://auth-server-default.com",
            "emea": "https://auth-server-emea.com",
            "apac": "https://auth-server-apac.com",
            "amer": "https://auth-server-amer.com",
        }
    }
)

Encryption key/secret

The secret_key_accessor variable is used to pass a key or secret used for data encryption.

Note: even though SDK uses PBKDF2 to generate a cryptographically strong encryption key, you must ensure that you provide a secret/password which follows the modern security best practices and standards.

The SecretKeyAccessor class constructor allows you to pass a function that should return either a string representing your secret or a dictionary (we call it secrets_data object):

{
  "secrets": [{
       "secret": str,
       "version": int, # Should be an integer greater than or equal to 0
       "isKey": bool,  # Should be True only for user-defined encryption keys
    }
  }, ....],
  "currentVersion": int,
}

The secrets_data variable allows you to specify multiple keys/secrets which the SDK will use for data decryption based on the version of the key or secret used for encryption. Meanwhile SDK will encrypt data by using only the key/secret pair which matches the currentVersion parameter provided in the secrets_data object.

This enables the flexibility required to support Key Rotation policies when secret/key must change with time. The SDK will encrypt data by using the current secret/key while maintaining the ability to decrypt data records that were encrypted with old key/secret. The SDK also provides a method for data migration which allows you to re-encrypt data with the newest key/secret. For details please see the migrate method.

The SDK allows you to use custom encryption keys, instead of secrets. Please note that a user-defined encryption key should be a 32-character 'utf8' encoded string as required by AES-256 cryptographic algorithm.

Below you can find several examples of how you can use SecretKeyAccessor module.

# Get a secret from a variable
from incountry import SecretKeyAccessor

password = "password"
secret_key_accessor = SecretKeyAccessor(lambda: password)

# Get secrets via an HTTP request
from incountry import SecretKeyAccessor
import requests as req

def get_secrets_data():
    url = "<your_secret_url>"
    r = req.get(url)
    return r.json() # assuming response is a `secrets_data` object

secret_key_accessor = SecretKeyAccessor(get_secrets_data)
Node.js SDK

To access your data in InCountry Platform by using Node.js SDK, you need to create an instance of the Storage class using the createStorage async factory method.

type StorageOptions = {
  apiKey?: string;          // Required when using API key authorization, or as the INC_API_KEY environment variable 
  environmentId?: string;   // Required to be passed in, or as the INC_ENVIRONMENT_ID environment variable 

  oauth?: {
    clientId?: string;      // Required when using oAuth authorization, can be also set through the INC_CLIENT_ID environment variable
    clientSecret?: string;  // Required when using oAuth authorization, can be also set through INC_CLIENT_SECRET environment variable
    authEndpoints?: {       // Custom endpoints regional map to use for fetching oAuth tokens
      default: string;
      [key: string]: string;
    };
  };

  endpoint?: string;        // Defines API URL
  encrypt?: boolean;        // If false, encryption is not used. Defaults to true.

  logger?: Logger;
  getSecrets?: Function;    // Used to fetch an encryption secret
  normalizeKeys?: boolean;
  countriesCache?: CountriesCache;
  hashSearchKeys?: boolean; // Set to false to enable partial match search among record's text fields `key1, key2, ..., key10`. Defaults to true.

  /**
   * Defines API base hostname part to use.
   * If set, all requests will be sent to https://${country}${endpointMask} host instead of the default
   * one (https://${country}-mt-01.api.incountry.io)
   */
  endpointMask?: string;

  /**
   * If your PoPAPI configuration relies on a custom PoPAPI server (rather than the default one)
   * use the `countriesEndpoint` option to specify the endpoint responsible for fetching the list of supported countries.
   */
  countriesEndpoint?: string;

  httpOptions?: {
    timeout?: NonNegativeInt; // Timeout in milliseconds.
  };
};

async function createStorage(
  options: StorageOptions,
  customEncryptionConfigs?: CustomEncryptionConfig[]
): Promise<Storage> {
  /* ... */
}

const { createStorage } = require('incountry');
const storage = await createStorage({
  apiKey: 'API_KEY',
  environmentId: 'ENVIRONMENT_ID',
  oauth: {
    clientId: '',
    clientSecret: '',
    authEndpoints: {
      default: 'https://auth',
    },
  },
  endpoint: 'INC_URL',
  encrypt: true,
  getSecrets: () => '',
  endpointMask: '',
  countriesEndpoint: '',
  httpOptions: {
    timeout: 5000,
  },
});

WARNING

API Key authorization is being deprecated. The backward compatibility is preserved for the apiKey parameter but you no longer can access API keys (neither old nor new) from your dashboard.


The oauth.clientId, oauth.clientSecret and environmentId variables can be fetched from your dashboard on InCountry Portal.

Otherwise you can create an instance of the Storage class and run all asynchronous checks by yourself (or skip their running at your own risk!).

const { Storage } = require('incountry');
const storage = new Storage({
  apiKey: 'API_KEY',
  environmentId: 'ENVIRONMENT_ID',
  endpoint: 'INC_URL',
  encrypt: true,
  getSecrets: () => '',
});

await storage.validate();

The validate method fetches the secret using GetSecretsCallback and validates it. If custom encryption configurations were provided they would also be checked with all the matching secrets.

oAuth Authentication

The SDK also supports oAuth authentication credentials instead of plain API key authorization. oAuth authentication flow is mutually exclusive with API key authentication - you will need to provide either API key or oAuth credentials.

Below you can find the example of how to create a storage instance with oAuth credentials (and also provide a custom oAuth endpoint):

const { Storage } = require('incountry');
const storage = new Storage({
  environmentId: 'ENVIRONMENT_ID',
  endpoint: 'INC_URL',
  encrypt: true,
  getSecrets: () => '',
  oauth: {
    clientId: 'CLIENT_ID',
    clientSecret: 'CLIENT_SECRET',
    authEndpoints: {
      "default": "https://auth-server-default.com",
      "emea": "https://auth-server-emea.com",
      "apac": "https://auth-server-apac.com",
      "amer": "https://auth-server-amer.com",
    },
  },
});

Encryption key/secret

The GetSecretsCallback function is used to pass a key or secret used for encryption.

Note: even though SDK uses PBKDF2 to generate a cryptographically strong encryption key, you must ensure that you provide a secret/password which follows the modern security best practices and standards.

The GetSecretsCallback function returns either a string representing your secret or an object (we call it SecretsData) or a Promise which is resolved to that string or object:

type SecretOrKey = {
  secret: string;
  version: NonNegativeInt;
  isKey?: boolean;
  isForCustomEncryption?: boolean;
};

type SecretsData = {
  currentVersion: NonNegativeInt;
  secrets: Array<SecretOrKey>;
};

/// SecretsData example
{
  secrets: [
    {
      secret: 'aaa',
      version: 0
    },
    {
      secret: 'base64...IHN0cmluZw==', // Should be a base64-encoded key (32 byte key)
      version: 1,
      isKey: true
    },
    {
      secret: 'ccc',
      version: 2,
      isForCustomEncryption: true
    }
  ],
  currentVersion: 1
};

The GetSecretsCallback function allows you to specify multiple keys/secrets which the SDK will use for decryption based on the version of the key or secret used for encryption. Meanwhile SDK will encrypt data only by using a key/secret pair which matches the currentVersion parameter provided in the SecretsData object.

This enables the flexibility required to support Key Rotation policies when secret/key pairs must be changed with time. The SDK will encrypt data by using the current secret/key pair while maintaining the ability to decrypt data records that were encrypted with old key/secret pairs. The SDK also provides a method for data migration which allows to re-encrypt data with the newest key/secret. For details please see the migrate method.

The SDK allows you to use custom encryption keys, instead of secrets. Please note that a user-defined encryption key should be a 32-character 'utf8' encoded string as required by AES-256 cryptographic algorithm.

Below you can find several examples of how you can use the GetSecretsCallback function.

type GetSecretsCallback = () => string | SecretsData | Promise<string> | Promise<SecretsData>;

// Synchronous
const getSecretsSync = () => 'longAndStrongPassword';

// Asynchronous
const getSecretsAsync = async () => {
  const secretsData = await getSecretsDataFromSomewhere();
  return secretsData;
};

// Using promises syntax
const getSecretsPromise = () =>
  new Promise(resolve => {
    getPasswordFromSomewhere(secretsData => {
      resolve(secretsData);
    });
  });
Java SDK

Use the StorageImpl class to access your data in InCountry Platofrm using Java SDK.

public class StorageImpl implements Storage {
  /**
   * creating Storage instance
   *
   * @param environmentID     Required to be passed in, or as the INC_API_KEY environment variable
                              with {@link #getInstance()}
   * @param apiKey            Required to be passed in, or as the INC_ENVIRONMENT_ID environment variable
                              with {@link #getInstance()}
   * @param endpoint          Optional. Defines API URL.
   *                          Default endpoint will be used if this parameter is null
   * @param secretKeyAccessor Instance of SecretKeyAccessor class. Used to fetch encryption secret
   * @return instance of Storage
   * @throws StorageClientException if configuration validation finishes with errors
   * @throws StorageServerException if server connection fails or server response error is returned
   */
  public static Storage getInstance(String environmentID, String apiKey, String endpoint,
                            SecretKeyAccessor secretKeyAccessor) throws StorageServerException {...}
//...
}

The environmentID and apiKey parameters (or clientId and clientSecret instead of apiKey) can be fetched from your dashboard on the Incountry Portal.

You can disable encryption (not recommended) by passing the null value to the secretKeyAccessor parameter.

Below you can find the example of how to create a new storage instance:

SecretKeyAccessor secretKeyAccessor = () -> SecretsDataGenerator.fromPassword("<password>");
String endPoint = "https://us-mt-01.api.incountry.io";
String envId = "<env_id>";
String apiKey = "<api_key>";
Storage storage = StorageImpl.getInstance(envId, apiKey, endPoint, secretKeyAccessor);

oAuth Authentication

SDK also supports oAuth authentication credentials instead of plain API key authorization. oAuth authentication flow is mutually exclusive with API key authentication - you will need to provide either API key or oAuth credentials.

Below you can find the example of how to create a new storage instance with oAuth credentials (and also provide a custom oAuth endpoint):

Map<String, String> authEndpointsMap = new HashMap<>();
authEndpointsMap.put("emea", "https://auth-server-emea.com");
authEndpointsMap.put("apac", "https://auth-server-apac.com");
authEndpointsMap.put("amer", "https://auth-server-amer.com");

StorageConfig config = new StorageConfig()
   //can be also set via environment variable INC_CLIENT_ID with {@link #getInstance()}
   .setClientId(CLIENT_ID)
   //can be also set via environment variable INC_CLIENT_SECRET with {@link #getInstance()}
   .setClientSecret(SECRET)
   .setAuthEndpoints(authEndpointsMap)
   .setDefaultAuthEndpoint("https://auth-server-default.com")
   .setEndpointMask(ENDPOINT_MASK)
   .setEnvId(ENV_ID);
Storage storage = StorageImpl.getInstance(config);

Note: the endpointMask parameter is used for switching from the default InCountry host list (api.incountry.io) to a different one. For example the endpointMask==-private.incountry.io setting will further redirect requests to https://{COUNTRY_CODE}-private.incountry.io If your PoPAPI configuration relies on a custom PoPAPI server (rather than the default one) use the countriesEndpoint option to specify the endpoint responsible for fetching the list of supported countries.

StorageConfig config = new StorageConfig()
   .setCountriesEndpoint(countriesEndpoint)
   //...
Storage storage = StorageImpl.getInstance(config);

Encryption key/secret

SDK provides the SecretKeyAccessor interface which allows you to pass your own secrets/keys to the SDK.

/**
 * Secrets accessor. Method {@link SecretKeyAccessor#getSecretsData()} is invoked on each encryption/decryption.
 */
public interface SecretKeyAccessor {

    /**
     * get your container with secrets
     *
     * @return SecretsData
     * @throws StorageClientException when something goes wrong during getting secrets
     */
    SecretsData getSecretsData() throws StorageClientException;
}


public class SecretsData {
    /**
     * creates a container with secrets
     *
     * @param secrets non-empty list of secrets. One of the secrets must have
     *        same version as currentVersion in SecretsData
     * @param currentVersion Should be a non-negative integer
     * @throws StorageClientException when parameter validation fails
     */
     public SecretsData(List<SecretKey> secrets, int currentVersion)
                throws StorageClientException {...}
    //...
}


public class SecretKey {
    /**
    * Creates a secret key
    *
    * @param secret  secret/key
    * @param version secret version, should be a non-negative integer
    * @param isKey   should be True only for user-defined encryption keys
    * @throws StorageClientException when parameter validation fails
    */
    public SecretKey(String secret, int version, boolean isKey)
              throws StorageClientException {...}
    //...
}

You can implement SecretKeyAccessor interface and pass secrets/keys in multiple ways:

  1. As a constant SecretsData object:

    SecretsData secretsData = new SecretsData(secretsList, currentVersion);
    SecretKeyAccessor accessor = () -> secretsData;
  2. As a function that dynamically fetches secrets:

    SecretKeyAccessor accessor = () -> loadSecretsData();
    
    private SecretsData loadSecretsData()  {
       String url = "<your_secret_url>";
       String responseJson = loadFromUrl(url).asJson();
       return SecretsDataGenerator.fromJson(responseJson);
    }

You can also use the SecretsDataGenerator class for creating SecretsData instances:

  1. from a String password:

    SecretsData secretsData = SecretsDataGenerator.fromPassword("<password>");
  2. from a JSON string representing SecretsData object:

    SecretsData secretsData = SecretsDataGenerator.fromJson(jsonString);
    {
    "secrets": [
        {
        "secret": "secret0",
        "version": 0,
        "isKey": false
        },
        {
        "secret": "secret1",
        "version": 1,
        "isKey": false
        }
    ],
    "currentVersion": 1
    }

The SecretsData object allows you to specify multiple keys/secrets which SDK will use for data decryption based on the version of the key or secret used for data encryption.

write-storage

كتابة البيانات للتخزين

.

  • Python SDK
  • Node.js SDK
  • Java SDK
  • REST API
Python SDK

Use the write method to create/replace a record (by record_key).

def write(self, country: str, record_key: str, **record_data: Union[str, int]) -> Dict[str, TRecord]:
    ...


# write returns created record dict on success
{
    "record": Dict
}

Below you can find the example of how you can use the write method.

write_result = storage.write(
    country="us",
    record_key="user_1",
    body="some PII data",
    profile_key="customer",
    range_key1=10000,
    key1="english",
    key2="rolls-royce",
)

# write_result would be as follows
write_result = {
    "record": {
        "record_key": "user_1",
        "body": "some PII data",
        "profile_key": "customer",
        "range_key1": 10000,
        "key1": "english",
        "key2": "rolls-royce",
    }
}

For the list of possible record_data kwargs, see the section below.

List of available record fields

v3.0.0 release introduced a series of new fields available for data storage. Below you can find the full list of all the fields available for storage in InCountry Platform along with their types and storage methods. Each field is either encrypted, hashed or stored as follows:

# String fields, hashed
record_key
key1
key2
key3
key4
key5
key6
key7
key8
key9
key10
profile_key
service_key1
service_key2

# String fields, encrypted
body
precommit_body

# Int fields, plain
range_key1
range_key2
range_key3
range_key4
range_key5
range_key6
range_key7
range_key8
range_key9
range_key10

Batches

Use the batch_write method to create/replace multiple records at once.

def batch_write(self, country: str, records: List[TRecord]) -> Dict[str, List[TRecord]]:
    ...


# batch_write returns the following dict of created records
{
    "records": List
}

Below you can find the example of how to use this method.

batch_result = storage.batch_write(
    country="us",
    records=[
        {"record_key": "key1", "body": "body1", ...},
        {"record_key": "key2", "body": "body2", ...},
    ],
)

# batch_result would be as follows
batch_result = {
    "records": [
        {"record_key": "key1", "body": "body1", ...},
        {"record_key": "key2", "body": "body2", ...},
    ]
}
Node.js SDK

Use the write method create/replace a record (by recordKey) .

List of available record fields

v3.0.0 release introduced a series of new fields available for data storage. Below you can find the full list of all the fields available for storage in InCountry Platform along with their types and storage methods. Each field is either encrypted, hashed or stored as follows:

String fields, hashed:
recordKey
profileKey
serviceKey1
serviceKey2
String fields, hashed if Storage options "hashSearchKeys" is set to true (by default it is):

WARNING If the hashSearchKeys option is set to false the following string fields will have length limitation of 256 characters at most.

key1
key2
key3
key4
key5
key6
key7
key8
key9
key10
String fields, encrypted:
body
precommitBody
Int fields, plain:
rangeKey1
rangeKey2
rangeKey3
rangeKey4
rangeKey5
rangeKey6
rangeKey7
rangeKey8
rangeKey9
rangeKey10
type StorageRecordData = {
  recordKey: string;
  profileKey?: string | null;
  key1?: string | null;  // If `hashSearchKeys` is set to `false` key1 has length limit 256
  key2?: string | null;  // If `hashSearchKeys` is set to `false` key2 has length limit 256
  key3?: string | null;  // If `hashSearchKeys` is set to `false` key3 has length limit 256
  key4?: string | null;  // If `hashSearchKeys` is set to `false` key4 has length limit 256
  key5?: string | null;  // If `hashSearchKeys` is set to `false` key5 has length limit 256
  key6?: string | null;  // If `hashSearchKeys` is set to `false` key6 has length limit 256
  key7?: string | null;  // If `hashSearchKeys` is set to `false` key7 has length limit 256
  key8?: string | null;  // If `hashSearchKeys` is set to `false` key8 has length limit 256
  key9?: string | null;  // If `hashSearchKeys` is set to `false` key9 has length limit 256
  key10?: string | null; // If `hashSearchKeys` is set to `false` key10 has length limit 256
  serviceKey1?: string | null;
  serviceKey2?: string | null;
  body?: string | null;
  precommitBody?: string | null;
  rangeKey1?: t.Int | null;
  rangeKey2?: t.Int | null;
  rangeKey3?: t.Int | null;
  rangeKey4?: t.Int | null;
  rangeKey5?: t.Int | null;
  rangeKey6?: t.Int | null;
  rangeKey7?: t.Int | null;
  rangeKey8?: t.Int | null;
  rangeKey9?: t.Int | null;
  rangeKey10?: t.Int | null;
};

type WriteResult = {
  record: StorageRecordData;
};

async write(
  countryCode: string,
  recordData: StorageRecordData,
  requestOptions: RequestOptions = {},
): Promise<WriteResult> {
  /* ... */
}

Below you can find the example of how you can use the write method.

const recordData = {
  recordKey: '<key>',
  body: '<body>',
  profileKey: '<profile_key>',
  rangeKey1: 0,
  key2: '<key2>',
  key3: '<key3>'
}

const writeResult = await storage.write(countryCode, recordData);

Batches

You can use the batchWrite method to create/replace multiple records at once.

type BatchWriteResult = {
  records: Array<StorageRecordData>;
};

async batchWrite(
  countryCode: string,
  records: Array<StorageRecordData>,
  requestOptions: RequestOptions = {},
): Promise<BatchWriteResult> {
  /* ... */
}

Example of usage:

batchResult = await storage.batchWrite(countryCode, recordDataArr);
Java SDK

Use the write method to create a new record.

public interface Storage {
    /**
     * Write data to a remote storage
     *
     * @param country country identifier
     * @param record  object which encapsulates data which must is written to the storage
     * @return recorded record
     * @throws StorageClientException if validation finishes with errors
     * @throws StorageServerException if server connection fails or a server response error occurs
     * @throws StorageCryptoException if encryption fails
     */
    Record write(String country, Record record)
          throws StorageClientException, StorageServerException, StorageCryptoException;
    //...
}

Below you can find the example of how to initialize a record object:

public class Record {
    /**
     * Full constructor
     *
     * @param key        Required, record key
     * @param body       Optional, data to be stored and encrypted
     * @param profileKey Optional, profile key
     * @param rangeKey   Optional, range key
     * @param key2       Optional, key2
     * @param key3       Optional, key3
     */
    public Record(String key, String body, String profileKey, Long rangeKey, String key2, String key3)
    //...
}

Below you can find the example of how to use the write method:

key = "user_1";
body = "some PII data";
profileKey = "customer";
rangeKey = 10000l;
key2 = "english";
key3 = "insurance";
Record record = new Record(key, body, profileKey, rangeKey, key2, key3);
storage.write("us", record);

Encryption

InCountry Platform uses the client-side encryption for your data. Note that only body is encrypted. Some other fields are hashed. Below you can find the example of how data is transformed and stored in InCountry Platform:

public class Record {
    private String key;          // hashed
    private String body;         // encrypted
    private String profileKey;   // hashed
    private Long rangeKey;       // plain
    private String key2;         // hashed
    private String key3;         // hashed
    //...
}

Batches

Use the batchWrite method to write multiple records to the storage within a single request.

public interface Storage {
     /**
      * Write multiple records at once in remote storage
      *
      * @param country country identifier
      * @param records record list
      * @return BatchRecord object which contains a list of recorded records
      * @throws StorageClientException if validation finishes with errors
      * @throws StorageServerException if server connection fails or a server response error occurs
      * @throws StorageCryptoException if record encryption fails
      */
     BatchRecord batchWrite(String country, List<Record> records)
          throws StorageClientException, StorageServerException, StorageCryptoException;
     //...
}

Below you can find the example of how you can use the batchWrite method:

List<Record> list = new ArrayList<>();
list.add(new Record(firstKey, firstBody, firstProfileKey, firstRangeKey, firstKey2, firstKey3));
list.add(new Record(secondKey, secondBody, secondProfileKey, secondRangeKey, secondKey2, secondKey3));
storage.batchWrite("us", list);
REST API

Create a record

POST /api/records

:::note Alternatively, this request can be used to update the record. Be careful, as it rewrites the data record with new values you provide. :::

Headers:

HeaderValue
Content-Typeapplication/json
Content-LengthCalculated automatically when a request is sent
HostCalculated automatically when a request is sent
User-AgentSpecify the user agent used
Accept/
Accept-Encodinggzip, deflate, br
Connectionkeep-alive

Request Body:

ParametersTypeDescription
record_keystringIt is used for storing the primary key of the record (identifier). The value from this field is also propagated to the key field when REST API creates a new record.
keyNstringCan be used for storing regulated and non-regulated data. Your record can have up to 10 keys.
profile_keystringAdditional data value
service_key1 service_key2stringService data value.
range_key1integerNumerical value. Your record can have up to 10 range keys.
bodystringAny text string that you want to store.
precommit_bodystringAny text string that you want to store. It can be used for storing the intermediate value of the body field.
countryISO country code (lowercase)Country which a new record is written to

Example:

{
  "record_key": "a98787ss87",
  "profile_key": "pk1237",
  "key1": "a1237",
  "key2": "John",
  "key3": "Doe",
  ...
  "key10": "Visa",
  "service_key1": "478",
  "service_key2": "57",
  "body": "This is a contact from Tradeshow",
  "precommit_body": "Consent received on 12/1/2020",
  "range_key1": 42,
  "range_key2": 50,
  ...
  "range_key10": 980,
  "country": "sg"
}

Responses:

STATUS 201 - application/json Returns information and a record key for the newly created record.

Example:

{
    "key": "a98787ss87",
    "profile_key": "pk1237",
    "range_key": 42,
    "record_key": "a98787ss87",
    "body": "This is a contact from Tradeshow",
    "precommit_body": "Consent received on 12/1/2020",
    "key1": "a1237",
    "key2": "John",
    "key3": "Doe",
    ...
    "key10": "Visa",
    "service_key1": "478",
    "service_key2": "57",
    "range_key1": 42,
    "range_key2": 50,
    ...
    "range_key10": 980,
    "country": "sg"
}

STATUS 400 - application/json Bad request when it has not passed validation. The error details will be in the response.

STATUS 401 - This error status is returned when the request is unauthorized.

reading-stored-data

قراءة البيانات المخزنة

.

  • Python SDK
  • Node.js SDK
  • Java SDK
  • REST API
Python SDK

You can read a stored record by its record_key by using the read method. It accepts an object with two fields - country and record_key.

def read(self, country: str, record_key: str) -> Dict[str, TRecord]:
    ...


# The read method returns the record dictionary if the record is found
{
    "record": Dict
}

Date fields

You can use the created_at and updated_at fields to access date-related information about records. The created_at field stores a date when a record was initially created in the target country. The updated_at field stores a date of the latest write operation for the given recordKey.

You can use the read method, as follows:

read_result = storage.read(country="us", record_key="user1")

# read_result would be as follows
read_result = {
    "record": {
        "record_key": "user_1",
        "body": "some PII data",
        "profile_key": "customer",
        "range_key1": 10000,
        "key1": "english",
        "key2": "rolls-royce",
        "created_at": datetime.datetime(...),
        "updated_at": datetime.datetime(...),
    }
}
Node.js SDK

You can read the stored data records by its recordKey by using the read method. It accepts an object with the two fields - country and recordKey. It returns a Promise which is resolved to { record } or is rejected if there are no records for the passed recordKey.

Date fields

You can use the createdAt and updatedAt fields to access date-related information about records. The createdAt field stores the date when the record was initially created in the target country. The updatedAt field stores the date of the latest write operation for the given recordKey.

type StorageRecord = {
  recordKey: string;
  body: string | null;
  profileKey: string | null;
  precommitBody: string | null;
  key1?: string | null;  // If `hashSearchKeys` is set to `false` key1 has length limit 256
  key2?: string | null;  // If `hashSearchKeys` is set to `false` key2 has length limit 256
  key3?: string | null;  // If `hashSearchKeys` is set to `false` key3 has length limit 256
  key4?: string | null;  // If `hashSearchKeys` is set to `false` key4 has length limit 256
  key5?: string | null;  // If `hashSearchKeys` is set to `false` key5 has length limit 256
  key6?: string | null;  // If `hashSearchKeys` is set to `false` key6 has length limit 256
  key7?: string | null;  // If `hashSearchKeys` is set to `false` key7 has length limit 256
  key8?: string | null;  // If `hashSearchKeys` is set to `false` key8 has length limit 256
  key9?: string | null;  // If `hashSearchKeys` is set to `false` key9 has length limit 256
  key10?: string | null; // If `hashSearchKeys` is set to `false` key10 has length limit 256
  serviceKey1: string | null;
  serviceKey2: string | null;
  rangeKey1: t.Int | null;
  rangeKey2: t.Int | null;
  rangeKey3: t.Int | null;
  rangeKey4: t.Int | null;
  rangeKey5: t.Int | null;
  rangeKey6: t.Int | null;
  rangeKey7: t.Int | null;
  rangeKey8: t.Int | null;
  rangeKey9: t.Int | null;
  rangeKey10: t.Int | null;
  createdAt: Date;
  updatedAt: Date;
}

type ReadResult = {
  record: StorageRecord;
};

async read(
  countryCode: string,
  recordKey: string,
  requestOptions: RequestOptions = {},
): Promise<ReadResult> {
  /* ... */
}

Example of usage:

const readResult = await storage.read(countryCode, recordKey);
Java SDK

You can read the stored records by key by using the read method.

public interface Storage {
   /**
    * Read data from remote storage
    *
    * @param country   country identifier
    * @param key record unique identifier
    * @return Record object which contains required data
    * @throws StorageClientException if validation finishes with errors
    * @throws StorageServerException if server connection fails or a server response error occurs
    * @throws StorageCryptoException if decryption fails
    */
    Record read(String country, String key)
        throws StorageClientException, StorageServerException, StorageCryptoException;
    //...
}

Below you can find the example of how you may use read method:

String key = "user_1";
Record record = storage.read("us", key);
String decryptedBody = record.getBody();

The Record object includes the following properties: key, body, key2, key3, profileKey, rangeKey.

These properties can be accessed using getters, for example:

String key2 = record.getKey2();
String body = record.getBody();
REST API

Find a record

POST /api/records/find

Headers:

HeaderValue
Content-Typeapplication/json
Content-LengthCalculated automatically when a request is sent
HostCalculated automatically when a request is sent
User-AgentSpecify the user agent used
Accept/
Accept-Encodinggzip, deflate, br
Connectionkeep-alive

Request Body:

ParametersTypeExampleDescription
countryISO country code (lowercase)sg (equals to Singapore)Country which a new record is looked up in. You can use different countries.
filterobjectSee the example.Specify the criteria for filtration within the filter parameter. You can look up for records by any of record fields. For the full list of supported fields, please see the Available record fields section.
keyNstringSee the example.Value from the key fields of your record.
search_keysstringabcDefines the search query string for lookup. The search keys should be provided within the filter object.
range_keyNarray[1, 5]Defines the range of keys for lookup. The range keys should be provided within the filter object.
optionsobjectSee the example.Specify the criteria for returning the search results:limit, offset
limitinteger10Sets the maximal number of search results.
offsetinteger50Specifies the offset (pagination) of records from which the record lookup is performed.

Example:

{
  "country": "sg",
  "filter": {
    "key1": ["k1234", "k1235"],
    "key2": ["John", "Jane"]
  },
  "options": {
    "limit": 10,
    "offset": 50
  }
}

or

{
  "country": "sg",
  "filter": {
    "search_keys": "abc"
  }
}

or

{
  "country": "sg",
  "filter": {
    "search_keys": "abc",
    "range_key1": [1, 2]
  }
}

Responses:

STATUS 200 - application/json Returns information about the found record or records.

This status is also returned when no records matching the search criteria are found.

Example:

{
    "data": [
        {
            "key": "k1235",
            "profile_key": "pk1235",
            "range_key": 42,
            "record_key": "k1235",
            "body": null,
            "precommit_body": null,
            "key1": null,
            "key2": "Jane",
            "key3": "Doe",
            "key4": null,
            "key5": null,
            "key6": null,
            "key7": null,
            "key8": null,
            "key9": null,
            "key10": null,
            "service_key1": null,
            "service_key2": null,
            "range_key1": 42,
            "range_key2": null,
            "range_key3": null,
            "range_key4": null,
            "range_key5": null,
            "range_key6": null,
            "range_key7": null,
            "range_key8": null,
            "range_key9": null,
            "range_key10": null,
            "version": 0,
            "created_at": "2020-11-21T12:07:43.000Z",
            "updated_at": "2020-11-21T12:07:43.000Z"
        },
        {
            "key": "k1234",
            "profile_key": "pk1234",
            "range_key": 42,
            "record_key": "k1234",
            "body": null,
            "precommit_body": null,
            "key1": null,
            "key2": "John",
            "key3": "Doe",
            "key4": null,
            "key5": null,
            "key6": null,
            "key7": null,
            "key8": null,
            "key9": null,
            "key10": null,
            "service_key1": null,
            "service_key2": null,
            "range_key1": 42,
            "range_key2": null,
            "range_key3": null,
            "range_key4": null,
            "range_key5": null,
            "range_key6": null,
            "range_key7": null,
            "range_key8": null,
            "range_key9": null,
            "range_key10": null,
            "version": 0,
            "created_at": "2020-11-21T11:55:49.000Z",
            "updated_at": "2020-11-21T12:05:28.000Z"
        }
    ],
    "meta": {
        "count": 2,
        "limit": 100,
        "offset": 0,
        "total": 2
    }
}

STATUS 400 - application/json Bad request when it has not passed validation. The error details will be in the response.

STATUS 401 - This error is returned when the request is unauthorized.

record-search

البحث عن السجلات

.

  • Python SDK
  • Node.js SDK
  • Java SDK
  • REST API
Python SDK

You can look up for records by keys or version using the find method.

def find(
        self,
        country: str,
        limit: Optional[int] = FIND_LIMIT,
        offset: Optional[int] = 0,
        **filters: Union[TIntFilter, TStringFilter],
    ) -> Dict[str, Any]:
    ...

Note: SDK returns 100 records at most.

The returned object looks like the following:

{
    "data": List,
    "errors": List, # optional
    "meta": {
        "limit": int,
        "offset": int,
        "total": int,  # total records matching filter, ignoring limit
    }
}

You can use the following options to look up for records by hashed string keys from the list above:

# single value
key1="value1" # records with key1 equal to "value1"

# list of values
key2=["value1", "value2"] # records with key2 equal to "value1" or "value2"

# dict with $not operator
key3={"$not": "value1"} # records with key3 not equal "value1"
key4={"$not": ["value1", "value2"]} # records with key4 equal to neither "value1" or "value2"

You can use the following options to look up for records by int keys from the list above:

# single value
range_key1=1 # records with range_key1 equal to 1

# list of values
range_key2=[1, 2] # records with range_key2 equal to 1 or 2

# dictionary with comparison operators
range_key3={"$gt": 1} # records with range_key3 greater than 1
range_key4={"$gte": 1} # records with range_key4 greater than or equal to 1
range_key5={"$lt": 1} # records with range_key5 less than 1
range_key6={"$lte": 1} # records with range_key6 less than or equal to 1

# you can combine different comparison operators
range_key7={"$gt": 1, "$lte": 10} # records with range_key7 greater than 1 and less than or equal to 10

# you cannot combine similar comparison operators - e.g. $gt and $gte, $lt and $lte

You can use the following option to look up for records by version (encryption key version):

# single value
version=1 # records with version equal to 1

# list of values
version=[1, 2] # records with version equal to 1 or 2

# dictionary with $not operator
version={"$not": 1} # records with version not equal 1
version={"$not": [1, 2]} # records with version equal to neither 1 or 2

Below you can find the example of how you can use the find method:

find_result = storage.find(country="us", limit=10, offset=10, key1="value1", key2=["value2", "value3"])

# find_result would be as follows
find_result = {
    "data": [
        {
            "record_key": "<record_key>",
            "body": "<body>",
            "key1": "value1",
            "key2": "value2",
            "created_at": datetime.datetime(...),
            "updated_at": datetime.datetime(...),
            ...
        }
    ],
    "meta": {
        "limit": 10,
        "offset": 10,
        "total": 100,
    }
}

Find one record matching a filter

If you need to find only one of the records matching a specific filter, you can use the find_one method.

def find_one(
        self, country: str, offset: Optional[int] = 0, **filters: Union[TIntFilter, TStringFilter],
    ) -> Union[None, Dict[str, Dict]]:
    ...


# If a record is not found, the find_one method returns `None`. Otherwise it returns a record dictionary.
{
    "record": Dict
}

Below you can find the example of how to use the find_one method:

find_one_result = storage.find_one(country="us", key1="english", key2=["rolls-royce", "bmw"])

# find_one_result would be as follows
find_one_result = {
    "record": {
        "record_key": "user_1",
        "body": "some PII data",
        "profile_key": "customer",
        "range_key1": 10000,
        "key1": "english",
        "key2": "rolls-royce",
    }
}
Node.js SDK

You can look up for data records either by using exact match search operators or partial text match operators in almost any combinations.

Exact match search

The following exact match search criteria are available:

  • single value:
// Matches all records where record.key1 = 'abc' AND record.rangeKey = 1
{ key1: 'abc', rangeKey1: 1 }
  • multiple values as an array:
// Matches all records where (record.key2 = 'def' OR record.key2 = 'jkl') AND (record.rangeKey = 1 OR record.rangeKey = 2)
{ key2: ['def', 'jkl'], rangeKey1: [1, 2] }
// Matches all records where record.key3 <> 'abc'
{ key3: { $not: 'abc' } }

// Matches all records where record.key3 <> 'abc' AND record.key3 <> 'def'
{ key3: { $not: ['abc', 'def'] } }

// Matches all records where record.version <> 1
{ version: { $not: 1 }}
// Matches all records where record.rangeKey >= 5 AND record.rangeKey <= 100
{ rangeKey1: { $gte: 5, $lte: 100 } }
Partial text match search

You can also look up for data records by partial match using the searchKeys operator which performs partial match search (similar to the LIKE SQL operator) within records text fields key1, key2, ..., key10.

// Matches all records where record.key1 LIKE 'abc' OR record.key2 LIKE 'abc' OR ... OR record.key10 LIKE 'abc'
{ searchKeys: 'abc' }

Please note: The searchKeys operator cannot be used in combination with any of key1, key2, ..., key10 keys and works only in combination with the non-hashing Storage mode (hashSearchKeys param for Storage).

// Matches all records where (record.key1 LIKE 'abc' OR record.key2 LIKE 'abc' OR ... OR record.key10 LIKE 'abc') AND (record.rangeKey = 1 OR record.rangeKey = 2)
{ searchKeys: 'abc', rangeKey1: [1, 2] }

// Causes validation error (StorageClientError)
{ searchKeys: 'abc', key1: 'def' }

Search options

The options parameter defines the limit - number of records that are returned, and the offset- the starting index used for record pagination. You can use these parameters to implement pagination. Note: The SDK returns 100 records at most.

type FilterStringValue = string | string[];
type FilterStringQuery = FilterStringValue | { $not?: FilterStringValue };

type FilterNumberValue = number | number[];
type FilterNumberQuery =
  FilterNumberValue |
  {
    $not?: FilterNumberValue;
    $gt?: number;
    $gte?: number;
    $lt?: number;
    $lte?: number;
  };

type FindFilter = Record<string, FilterStringQuery | FilterNumberQuery>;

type FindOptions = {
  limit?: number;
  offset?: number;
};

type FindResult = {
  meta: {
    total: number;
    count: number;
    limit: number;
    offset: number;
  };
  records: Array<StorageRecord>;
  errors?: Array<{ error: StorageCryptoError; rawData: ApiRecord }>;
};

async find(
  countryCode: string,
  filter: FindFilter = {},
  options: FindOptions = {},
  requestOptions: RequestOptions = {},
): Promise<FindResult> {
  /* ... */
}

Example of usage

const filter = {
  key1: 'abc',
  key2: ['def', 'jkl'],
  profileKey: 'test2',
  rangeKey1: { $gte: 5, $lte: 100 },
  rangeKey2: { $not: [0, 1] },
}

const options = {
  limit: 100,
  offset: 0,
};

const findResult = await storage.find(countryCode, filter, options);

The returned findResult object looks like the following:

{
  records: [{/* StorageRecord */}],
  errors: [],
  meta: {
    limit: 100,
    offset: 0,
    total: 24
  }
}

Find one record matching a filter

If you need to find only one of the records matching a specific filter, you can use the find_one method. If a record is not found, it returns null.

type FindOneResult = {
  record: StorageRecord | null;
};

async findOne(
  countryCode: string,
  filter: FindFilter = {},
  options: FindOptions = {},
  requestOptions: RequestOptions = {},
): Promise<FindOneResult> {
  /* ... */
}

Example of usage:

const findOneResult = await storage.findOne(countryCode, filter);
Java SDK

You can search for data records by random keys using the find method.

public interface Storage {
   /**
    * Find records in remote storage according to filters
    *
    * @param country country identifier
    * @param builder object representing find filters and search options
    * @return BatchRecord object which contains required records
    * @throws StorageClientException if validation finishes with errors
    * @throws StorageServerException if server connection fails or a server response error occurs
    * @throws StorageCryptoException if decryption fails
    */
    BatchRecord find(String country, FindFilterBuilder builder)
         throws StorageClientException, StorageServerException, StorageCryptoException;
    //...
}

You can use the FindFilterBuilder class to refine your find request.

Below you can find the example of how to use the find method along with the FindFilterBuilder class:

FindFilterBuilder builder = FindFilterBuilder.create()
                  .key2Eq("someKey")
                  .key3Eq("firstValue","secondValue")
                  .rangeKeyBetween(123l, 456l);

BatchRecord findResult = storage.find("us", builder);
if (findResult.getCount() > 0) {
    Record record = findResult.getRecords().get(0);
    //...
}

The request will return records that are filtered according to the following pseudo-sql:

key2 = 'someKey' AND key3 in ('firstValue' , 'secondValue') AND (123 < = `rangeKey` < = 456)

All conditions added with the FindFilterBuilder class are joined using the logical operator AND. You cannot add multiple conditions for the same key, elsewise only the last condition will be used.

The SDK returns 100 records at most. Use the limit and offset parameters to iterate through the list of records.

FindFilterBuilder builder = FindFilterBuilder.create()
                  //...
                  .limitAndOffset(20, 80);
BatchRecord records = storage.find("us", builder);

Next predicate types are available for each string key field of the Record class through individual methods of the FindFilterBuilder class:

EQUALS         (FindFilterBuilder::keyEq)
               (FindFilterBuilder::key2Eq)
               (FindFilterBuilder::key3Eq)
               (FindFilterBuilder::profileKeyEq)
NOT_EQUALS     (FindFilterBuilder::keyNotEq)
               (FindFilterBuilder::key2NotEq)
               (FindFilterBuilder::key3NotEq)
               (FindFilterBuilder::profileKeyNotEq)

You can use the following builder methods for record filtering by the numerical rangeKey field:

EQUALS              (FindFilterBuilder::rangeKeyEq)
IN                  (FindFilterBuilder::rangeKeyIn)
GREATER             (FindFilterBuilder::rangeKeyGT)
GREATER OR EQUALS   (FindFilterBuilder::rangeKeyGTE)
LESS                (FindFilterBuilder::rangeKeyLT)
LESS OR EQUALS      (FindFilterBuilder::rangeKeyLTE)
BETWEEN             (FindFilterBuilder::rangeKeyBetween)

The find method returns the BatchRecord object which contains a list of Record and some metadata:

class BatchRecord {
    private int count;
    private int limit;
    private int offset;
    private int total;
    private List<Record> records;
    //...
}

These fields can be accessed using getters, for example:

int limit = records.getTotal();

The BatchRecord.getErrors() method allows you to get a list of the RecordException objects with detailed information about records that were not correctly processed during execution of the find request.

Find one record matching a filter

If you need to find the first record matching filter, you can use findOne method:

public interface Storage {
   /**
    * Find only one first record in remote storage according to filters
    *
    * @param country country identifier
    * @param builder object representing find filters
    * @return founded record or null
    * @throws StorageClientException if validation finishes with errors
    * @throws StorageServerException if server connection fails or a server response error occurs
    * @throws StorageCryptoException if decryption fails
    */
    Record findOne(String country, FindFilterBuilder builder)
           throws StorageClientException, StorageServerException, StorageCryptoException;
    //...
}

It works the same way as the find method but returns the first record or null if no records found.

Below you can find the example of how the findOne method can be used:

FindFilterBuilder builder = FindFilterBuilder.create()
                .key2Eq("someKey")
                .key3Eq("firstValue", "secondValue")
                .rangeKeyBetween(123l, 456l);

Record record = storage.findOne("us", builder);
//...
REST API

Find a record

POST /api/records/find

Headers:

HeaderValue
Content-Typeapplication/json
Content-LengthCalculated automatically when a request is sent
HostCalculated automatically when a request is sent
User-AgentSpecify the user agent used
Accept/
Accept-Encodinggzip, deflate, br
Connectionkeep-alive

Request Body:

ParametersTypeExampleDescription
countryISO country code (lowercase)sg (equals to Singapore)Country which a new record is looked up in. You can use different countries.
filterobjectSee the example.Specify the criteria for filtration within the filter parameter. You can look up for records by any of record fields. For the full list of supported fields, please see the Available record fields section.
keyNstringSee the example.Value from the key fields of your record.
search_keysstringabcDefines the search query string for lookup. The search keys should be provided within the filter object.
range_keyNarray[1, 5]Defines the range of keys for lookup. The range keys should be provided within the filter object.
optionsobjectSee the example.Specify the criteria for returning the search results:limit, offset
limitinteger10Sets the maximal number of search results.
offsetinteger50Specifies the offset (pagination) of records from which the record lookup is performed.

Example:

{
  "country": "sg",
  "filter": {
    "key1": ["k1234", "k1235"],
    "key2": ["John", "Jane"]
  },
  "options": {
    "limit": 10,
    "offset": 50
  }
}

or

{
  "country": "sg",
  "filter": {
    "search_keys": "abc"
  }
}

or

{
  "country": "sg",
  "filter": {
    "search_keys": "abc",
    "range_key1": [1, 2]
  }
}

Responses:

STATUS 200 - application/json Returns information about the found record or records.

This status is also returned when no records matching the search criteria are found.

Example:

{
    "data": [
        {
            "key": "k1235",
            "profile_key": "pk1235",
            "range_key": 42,
            "record_key": "k1235",
            "body": null,
            "precommit_body": null,
            "key1": null,
            "key2": "Jane",
            "key3": "Doe",
            "key4": null,
            "key5": null,
            "key6": null,
            "key7": null,
            "key8": null,
            "key9": null,
            "key10": null,
            "service_key1": null,
            "service_key2": null,
            "range_key1": 42,
            "range_key2": null,
            "range_key3": null,
            "range_key4": null,
            "range_key5": null,
            "range_key6": null,
            "range_key7": null,
            "range_key8": null,
            "range_key9": null,
            "range_key10": null,
            "version": 0,
            "created_at": "2020-11-21T12:07:43.000Z",
            "updated_at": "2020-11-21T12:07:43.000Z"
        },
        {
            "key": "k1234",
            "profile_key": "pk1234",
            "range_key": 42,
            "record_key": "k1234",
            "body": null,
            "precommit_body": null,
            "key1": null,
            "key2": "John",
            "key3": "Doe",
            "key4": null,
            "key5": null,
            "key6": null,
            "key7": null,
            "key8": null,
            "key9": null,
            "key10": null,
            "service_key1": null,
            "service_key2": null,
            "range_key1": 42,
            "range_key2": null,
            "range_key3": null,
            "range_key4": null,
            "range_key5": null,
            "range_key6": null,
            "range_key7": null,
            "range_key8": null,
            "range_key9": null,
            "range_key10": null,
            "version": 0,
            "created_at": "2020-11-21T11:55:49.000Z",
            "updated_at": "2020-11-21T12:05:28.000Z"
        }
    ],
    "meta": {
        "count": 2,
        "limit": 100,
        "offset": 0,
        "total": 2
    }
}

STATUS 400 - application/json Bad request when it has not passed validation. The error details will be in the response.

STATUS 401 - This error is returned when the request is unauthorized.

delete-data

حذف سجلات البيانات

.

  • Python SDK
  • Node.js SDK
  • Java SDK
  • REST API
Python SDK

You can use the delete method to delete a record from InCountry Platform. It is possible by using the record_key field only.

def delete(self, country: str, record_key: str) -> Dict[str, bool]:
    ...


# the delete method returns the following dictionary upon success
{
    "success": True
}

Below you can find the example of how to use the delete method:

delete_result = storage.delete(country="us", record_key="<record_key>")

# delete_result would be as follows
delete_result = {
    "success": True
}
Node.js SDK

You can use the delete method to delete a record from InCountry Platform. It is possible by using the record_key field only.

type DeleteResult = {
  success: true;
};

async delete(
  countryCode: string,
  recordKey: string,
  requestOptions: RequestOptions = {},
): Promise<DeleteResult> {
  /* ... */
}

Example of usage:

const deleteResult = await storage.delete(countryCode, recordKey);
Java SDK

Use the delete method to delete records from InCountry storage. It is only possible by using the key field.

public interface Storage {
    /**
    * Delete a record from remote storage
    *
    * @param country   country code of the record
    * @param key the record's key
    * @return true when record was deleted
    * @throws StorageClientException if validation finishes with errors
    * @throws StorageServerException if server connection fails
    */
    boolean delete(String country, String key)
            throws StorageClientException, StorageServerException;
    //...
}

Below you can find the the example of how you may use the delete method:

String key = "user_1";
storage.delete("us", key);
REST API

Delete a record

DELETE /api/records/:country/:key

Headers:

HeaderValue
HostCalculated automatically when a request is sent
User-AgentSpecify the user agent used
Accept/
Accept-Encodinggzip, deflate, br
Connectionkeep-alive

Request parameters:

ParametersTypeValueDescription
countryISO country code (lowercase)sgCountry which a new record is removed from
keystring/arrayk1234Unique primary key for record identification

Responses:

STATUS 204 - plain This response is returned when the record has been successfully deleted.

STATUS 401- This response is returned when the request is unauthorized.

STATUS 404 - plain This response is returned when the record is not found.

هل لديك أسئلة؟

يمكنك زيارة صفحة الأسئلة الشائعة الخاصة بالمطورين للتعرف على إجابات لأسئلتك.