Разработчикам

Платформа InCountry предоставляет разработчикам возможность настраивать локализацию данных и управлять ею. Благодаря множеству инструментов InCountry интегрируется практически с любым типом приложений.

Набор инструментов для разработчиков InCountry включает SDK, API, прокси и ряд готовых интеграций для SaaS с охватом более чем 90 стран.

Платформа InCountry

Diagram Developers

Инструменты InCountry

Платформа InCountry предоставляет различные продукты для услуг хранения данных, а именно:

Портал InCountry .

Портал InCountry — это начальная точка для начала работы с платформой InCountry. Он предоставляет возможность регистрации для создания учетной записи организации и дальнейшего управления хранилищами данных. Здесь вы также можете управлять учетными записями других сотрудников в вашей организации, которые получают доступ к этим хранилищам данных и выполняют определенные операции.

Граница внутри страны .

InCountry Border — это компонент передачи данных, который интегрируется между внешней и внутренней частями вашего приложения. Он передает запросы с регулируемыми данными из внешнего интерфейса в бэкэнд и записывает их на платформу InCountry, возвращая токенизированные данные в бэкэнд. Это позволяет редактировать и не редактировать данные на лету с минимальными усилиями.

InCountry REST API .

InCountry REST API — это набор методов RESTful, которые позволяют запрашивать регулируемые данные на платформе InCountry без интеграции InCountry SDK в ваше приложение. Он упрощает обмен данными и позволяет быстро создавать приложения с возможностями локализации данных.

InCountry SDK .

InCountry SDK — это набор инструментов разработки программного обеспечения для следующих языков программирования:

  • Python
  • Java
  • Node.js
  • .Net (скоро)

Вы можете интегрировать InCountry SDK в свое приложение или систему, чтобы разработать сложные сценарии обработки данных и использовать все преимущества платформы InCountry для локализации данных. С помощью InCountry SDK вы можете реализовать комплексную интеграцию и обрабатывать регулируемые данные так, как вам нужно. Кроме того, с помощью InCountry SDK вы можете самостоятельно управлять ключами шифрования и при необходимости использовать собственные методы шифрования.

Интеграции SaaS (включая Salesforce) .

Платформа InCountry обеспечивает множество встроенных интеграций с наиболее эффективными решениями SaaS, включая Salesforce и ServiceNow. Вы можете использовать встроенную интеграцию для обработки регулируемых данных из приложений SaaS. Воспользуйтесь преимуществами существующих интеграций и пакетов, настройте их в соответствии со своими данными и потребностями.

Благодаря интеграции InCountry для Salesforce вы можете редактировать регулируемые данные в Salesforce при доступе к ним за пределами страны происхождения, ограничивать доступ к ним для пользователей из определенных стран или реплицировать их в другие страны без нарушения местных нормативных требований.

Особенности продуктов InCountry

В следующем разделе содержится список функций, которые предоставляет набор инструментов InCountry:

InCountry Portal .

  • Первоначальная регистрация новых пользователей и восстановление аккаунта
  • Руководство организации и ее члены
  • Управление доступом к учетной записи организации
  • Создание сред хранения и учетные данные для доступа
  • Модуль рекомендаций для хранения регулируемых данных для каждой страны

InCountry Border .

  • Полная интеграция между интерфейсом и серверной частью вашего приложения
  • Политики редактирования и неотредактирования для регулируемых типов данных
  • Редактирование / удаление данных на лету
  • Пользовательский контроль данных
  • Токенизация регулируемых данных с сохранением форматов данных
  • Шифрование регулируемых данных в платформе InCountry

InCountry REST API .

  • Быстрое создание прототипа приложения
  • Все операции CRUD под рукой
  • Шифрование регулируемых данных в платформе InCountry
  • Получение данных по запросу для экономии полосы пропускания данных
  • Бессерверные функции для обработки, проверки и агрегирования данных

InCountry SDK .

  • Доступность для трех языков программирования
  • Реализация сложных сценариев данных в вашем приложении
  • Шифрование данных так, как вам нужно
  • Высокая производительность и быстрый доступ к данным
  • Перенос данных и ротация ключей
  • Пользовательские методы шифрования
  • Запись / чтение данных партиями
  • Безопасная передача данных с помощью TLS 1.2.

Интеграции SaaS (включая Salesforce) .

  • Мгновенная интеграция с приложениями SaaS, включая Salesforce
  • Минимальное время на соответствие требованиям к резидентности данных
  • Бессерверные функции с JavaScript
  • Шифрование данных
  • Несколько режимов обработки данных: редактирование, ограничение, репликация

Выбираем лучшее решение для ваших нужд

Создание нового приложения .

Для создания нового приложения, прежде всего, определите уровень интеграции и поток данных между вашим приложением и платформой InCountry. В дополнение к этому также определите, какую модель регулирования данных вы хотите использовать и какие страны будет обслуживать ваше приложение.

Как только все эти точки определены, вы можете перейти к выбору инструмента InCountry следующим образом:

1. Если ваше приложение имеет только внешний интерфейс и серверную часть с простым потоком данных для передачи данных 135792, самым простым решением для вас будет его интеграция с InCountry Border. Вам просто нужно будет перенаправить свои запросы данных с внешнего интерфейса на InCountry Proxy и далее направить их на серверную часть.

2. Если вы хотите реализовать более автономное приложение, вам необходимо использовать InCountry REST API. Вам нужно будет авторизовать запросы данных с помощью выданного сертификата на стороне клиента вашего приложения.

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 API позволит вам сделать это с минимальными корректировками в потоке передачи данных.

REST API может интегрировать несколько систем и выполнять запросы данных между системами как с регулируемыми, так и с нерегулируемыми данными. Таким образом вы можете комбинировать данные для отображения в ваших системах.

Создание резервной копии данных .

Для создания резервных копий записей с регулируемыми данными используйте InCountry REST API. Он позволяет выполнять массовый импорт записей в платформу InCountry. Вы можете хранить эти записи в течение длительного времени, имея к ним доступ по запросу, когда это необходимо.

Размещение данных для решений SaaS .

Если вы используете какое-либо решение SaaS, например Salesforce, используйте наши собственные решения интеграции SaaS для хранения данных и безопасного хранения данных на платформе InCountry. Быстрое развертывание и минимальная настройка — ключевые преимущества готовых решений. Бессерверные функции позволят вам улучшить и изменить операции передачи данных для ваших регулируемых данных.

На данный момент платформа InCountry предоставляет следующие готовые возможности интеграции:

  • Salesforce
  • ServiceNow
  • Мамбу
  • Сегмент
  • Twilio

Модели регулирования данных

Платформа InCountry может быть сконфигурирована в трех моделях в зависимости от нормативных требований, соответствия требованиям и потребностей клиентов.

Модель

Описание

Редактирование

Регулируемые данные хранятся в стране происхождения и не могут покинуть ее. Доступ к этим данным регулируется в зависимости от местонахождения текущего пользователя, поэтому конечный пользователь увидит либо регулируемые данные (если страна доступа совпадает со страной происхождения данных), либо отредактированные данные (если страна доступа отличается от страны происхождения данных) .

Все запросы данных для регулируемых данных выполняются непосредственно на платформе InCountry в стране происхождения. Регулируемые данные не сохраняются в вашей системе.

Ограничение

Регулируемые данные хранятся на платформе InCountry в стране происхождения, но в некоторых случаях могут покидать платформу после прочтения.

Все запросы данных для регулируемых данных выполняются непосредственно на платформу InCountry в стране происхождения, когда вы запрашиваете эти данные за пределами страны происхождения. Регулируемые данные не сохраняются в вашей системе.

Репликация

Регулируемые данные сначала сохраняются в стране происхождения, затем данные могут быть реплицированы в другие страны. В этом случае регулируемые данные сохраняются на платформе 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 API использует сертификат для авторизации запросов данных к платформе InCountry. Команда InCountry предоставит вам сертификат по вашему запросу.

Быстрые рецепты

Ниже вы можете найти списки быстрого кода для того, чтобы реализовать то или иное решение с платформой InCountry.

Create Storage

Создание экземпляра хранилища.

Чтобы начать работу с платформой InCountry, вам необходимо создать экземпляр хранилища:

  • 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.

Writing data to 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.

Чтение сохраненных данных

Чтение сохраненных данных.

  • 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.

Looking up for records

Поиск записей.

  • 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.

Deleting data records

Удаление записей данных.

  • 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.

Есть вопросы?

Вы можете посетить нашу страницу часто задаваемых вопросов для разработчиков, чтобы узнать ответы на свои вопросы.