开发者

InCountry平台使开发人员能够配置和管理数据本地化。借助大量工具,InCountry几乎可以与任何类型的应用程序集成。

InCountry的开发人员工具集包括一个SDK,一个API,一个代理以及一系列SaaS的现成集成,覆盖了90多个国家。

国内平台

diagram-developers

国内工具

InCountry Platform提供了多种用于数据驻留服务的产品,如下所示:

国内门户 .

InCountry Portal是在InCountry平台内入门的起点。它提供了注册选项来创建组织帐户,并进一步控制您的数据存储。在这里,您还可以管理组织中有权访问这些数据存储并执行特定操作的其他员工的帐户。

国家边境 .

InCountry Border是一个数据通信组件,集成在应用程序的前端和后端之间。它使用从前端到后端的受监管数据代理请求,并将其写入InCountry平台,同时将标记化数据返回到后端。它使您可以快速地编辑和取消编辑数据。

InCountry REST API .

InCountry REST API是一组RESTful方法,允许您在InCountry Platform中查询受管制的数据,而无需将InCountry SDK集成到您的应用程序中。它简化了数据通信,并允许您使用数据本地化功能快速构建应用程序。

InCountry SDK .

InCountry SDK是用于以下编程语言的软件开发工具的集合:

  • Python
  • Java
  • Node.js
  • .Net(即将推出)

您可以将InCountry SDK集成到您的应用程序或系统中,以开发复杂的数据处理方案,并使用InCountry Platform的所有优势进行数据本地化。 借助InCountry SDK,您可以按照需要的方式实施全面的集成并处理受监管的数据。 此外,使用InCountry SDK,您可以自己管理加密密钥,并在需要时使用自定义加密方法。

SaaS集成(包括Salesforce) .

InCountry平台提供了各种与性能最高的SaaS解决方案的本机集成,包括Salesforce和ServiceNow。您可以使用现成的集成来处理来自SaaS应用程序的受管制数据。利用现有的集成和软件包,根据您的数据和需求自定义它们。

使用InCountry for Salesforce集成,您可以在访问原始国家/地区之外的数据时在Salesforce中编辑受管制的数据,限制来自特定国家/地区的用户对其的访问,或者将其复制到其他国家/地区,而不会违反本地合规性规定。

国内产品的特点

以下部分包含InCountry工具集提供的功能列表:

InCountry Portal .

  • 初始注册新用户和帐户恢复
  • 组织及其成员的管理
  • 组织帐户的访问管理
  • 创建存储环境和访问凭证
  • 推荐模块,用于存储每个国家的管制数据

InCountry Border .

  • 应用程序的前端和后端之间的无缝集成
  • 规范数据类型的修订和取消修订策略
  • 即时数据编辑/取消编辑
  • 基于用户的数据控制
  • 使用保留数据格式对监管数据进行标记化
  • InCountry平台中的受监管数据加密

InCountry REST API .

  • 快速创建应用程序原型
  • 现有的所有CRUD操作
  • InCountry平台中的受监管数据加密
  • 按需数据检索可节省数据带宽
  • 无服务器功能,用于处理,验证和汇总数据

InCountry SDK .

  • 三种编程语言的可用性
  • 在您的应用程序中实现复杂的数据方案
  • 所需方式进行数据加密
  • 强大的性能和快速的数据访问
  • 数据迁移和密钥轮换
  • 自定义加密方法
  • 批量写入/读取数据
  • 使用TLS 1.2进行安全的数据通信。

SaaS集成(包括Salesforce) .

  • 与SaaS应用程序(包括Salesforce)即时集成
  • 最少的数据居留时间
  • JavaScript的无服务器功能
  • 数据加密
  • 多种数据处理模式:编校,限制,复制

选择最适合您需求的解决方案

建立一个新的应用程序 .

对于构建新应用程序,首先,确定应用程序和InCountry Platform之间的集成级别和数据通信流。除此之外,还要确定您要使用哪种数据法规模型以及您的应用程序将服务于哪些国家。

一旦确定了所有这些点,就可以继续选择InCountry工具,如下所示:

1. 如果您的应用程序只有前端和后端带有a135792简单数据通信数据流,那么最简单的解决方案是将其与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 REST API执行常见请求,从而将一个系统中的受管制数据写入InCountry Platform,并从另一系统中读取该数据。管制数据的标记化将由InCountry平台执行,因此您无需在系统上进行任何调整,只需存储用于区分记录的记录键即可。

同样,您可以集成更多系统,例如,您可以拥有一个CRM系统和一个ERP系统,这些系统存储自己的数据层,您希望在您的数据聚合系统中进行合并和分析。 InCountry REST API将允许您在数据通信流程中进行最小的调整。

REST API可以集成多个系统,并使用系统之间的管制和非管制数据执行数据查询。这样,您可以合并数据以在系统中显示它们。

创建数据备份 .

若要使用管制数据创建记录的数据备份,请使用InCountry REST API。它使您可以将记录批量导入InCountry Platform。您可以长时间存储这些记录,并在需要时按需访问它。

SaaS解决方案的数据驻留 .

如果您使用任何SaaS解决方案(例如Salesforce),请使用我们的本机SaaS集成解决方案在InCountry Platform中实现数据驻留和安全的数据保留。快速部署和最少的设置是即用型解决方案的主要优势。无服务器功能将使您可以增强和更改受管制数据的数据通信操作。

目前,InCountry Platform提供了以下现成的集成:

  • 销售队伍
  • 现在服务
  • 马姆布
  • 分割
  • 特威里奥

数据法规模型

根据法规,合规性和客户需求,可以将InCountry平台配置为三种模型。

模型

描述

编辑

管制数据存储在原产国内,不能离开。根据当前用户的位置来管理对这些数据的访问,因此最终用户将看到受监管的数据(如果访问国家与数据的原始国家相匹配)或已编辑的数据(如果访问国家与数据的原始国家不同) 。

所有对受管制数据的数据请求都直接发送到原籍国的InCountry平台。管制数据未保存在系统内。

限制

管制数据存储在来源国的InCountry平台内,但在某些情况下在读取时会保留下来。

当您在原始国家/地区之外请求此数据时,将直接向原始国家/地区中的InCountry Platform执行所有对受管制数据的数据请求。管制数据未保存在系统内。

复写

管制数据首先保存在原籍国,然后可以将数据复制到其他国家。在这种情况下,受管制的数据将保存到位于原始国家/地区的InCountry Platform中,然后将其复制到另一个国家/地区的系统或数据存储中。

下一步

在下面,您可以找到可立即使用的程序样本的链接,这些样本可用于自己构建应用程序。

1.
授权书

InCountry Platform会根据您使用的产品提供不同的方法来授权数据请求。

2.
OAuth 2.0授权

InCountry Platform使用OAuth 2.0协议进行身份验证和授权。 InCountry Platform支持常见的OAuth 2.0方案,例如用于Web服务器和客户端的方案。 OAuth 2.0授权机制可确保您帐户的安全性,并提供对用户会话的完全控制。

要开始使用InCountry平台,请在InCountry Portal上创建一个新帐户。 在这里,您需要创建一个新的环境和客户端。 对于每个客户端,您将获得客户端ID和客户端密钥,可将其进一步用于从InCountry平台获取令牌。

3.
获取客户端ID和客户端机密

1.注册或登录InCountry Portal。

2.创建一个新环境。

3.创建一个新客户端。

4. InCountry Portal将发布环境的客户端ID和客户端密钥。

5.使用颁发的客户端和客户端密钥通过SDK生成令牌。

4.
证书授权

InCountry REST API使用证书来授权对InCountry Platform的数据请求。 InCountry团队将根据您的要求向您提供证书。

快速食谱

在下面,您可以找到有关如何使用InCountry Platform实施此解决方案的快速代码食谱的列表。

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.

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.

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.

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.

有问题吗?

您可以访问我们的开发人员常见问题解答页面以找到问题的答案。