# Configure the SDK

Each Ripple Custody SDK reads its settings from a `config.toml` file, from `RC_*` environment variables, or from values you set in code. This page describes where each SDK looks, which keys and variables it recognizes, and how to configure TLS. For a minimal working setup, see [Integrate via SDK](/pt-br/products/custody/get-started/integrate-via-sdk).

## Configuration sources and precedence

In all three SDKs, an `RC_*` environment variable overrides the matching key in `config.toml`, and a key in the file overrides the built-in default. This order lets you keep non-secret settings in a file and supply secrets and per-environment values through the environment.

|  | TypeScript | Java | Python |
|  --- | --- | --- | --- |
| Load from file and environment | `loadConfiguration()` or `loadConfiguration(path)` | `CustodyClientConfig.loadConfiguration()` or `loadConfiguration(Path)` | `load_configuration()` or `load_configuration(config_file)` |
| Load from environment only | Call `loadConfiguration()` with no file present | `CustodyClientConfig.fromEnvironment()` | Call `load_configuration()` with no file present |
| Configure in code | `generateNewConfiguration()` and a `Configuration` object | `ApiClient.newBuilder()` and `CustodyClientConfig` setters | `Configuration(...)` |


### Configuration file location

All three SDKs search for `config.toml` in the following order and use the first file that exists:

1. The path you pass to `loadConfiguration` (`load_configuration` in Python).
2. The path in `RC_CONFIG_FILE`.
3. `./config.toml` in the working directory.
4. `~/.config/ripple-custody/config.toml`.


If an SDK finds no file, it continues with environment variables only. It fails only when a required setting is missing from every source. In Java, a named file replaces the default locations: the SDK doesn't search them.

### Configuration file format

All keys are top-level `key = value` pairs. The SDKs share one set of key names, so the same `config.toml` file works in each language. To get a key name, drop the `RC_` prefix from the environment variable and lowercase the rest, for example `RC_API_BASE_URL` becomes `api_base_url`. The following keys keep the prefix: `rc_user_id`, `rc_domain_id`, `rc_validate_addresses`, `rc_verify_derived_addresses`, and `rc_enforce_immutable_endpoints`.

- **Unknown keys.** All three SDKs reject a `config.toml` file with keys that they don't recognize. The error names the key and, for a renamed key, the name to use instead.
- **Secrets.** Set `RC_CLIENT_SECRET` as an environment variable. It has no configuration file key, and the Java SDK rejects `client_secret` in the file.
- **TOML support.** The Java SDK parses a subset of TOML: strings, booleans, and numbers. It doesn't accept arrays or multi-line strings. To put a PEM certificate in `ca_cert_data`, write it as one string with `\n` escapes.


## Environment variable and key reference

The following tables list the `RC_*` environment variables by area, with the `config.toml` key for each. "Environment only" means the setting has no configuration file key.

### Connection and authentication

| Variable | `config.toml` key | Purpose |
|  --- | --- | --- |
| `RC_CONFIG_FILE` | Environment only | Path to the configuration file. |
| `RC_API_BASE_URL` | `api_base_url` | Base URL of the Ripple Custody API. Required. |
| `RC_OIDC_ISSUER_URL` | `oidc_issuer_url` | OpenID Connect (OIDC) issuer URL. The SDK discovers the token endpoint from the issuer's `/.well-known/openid-configuration` document. |
| `RC_TOKEN_ENDPOINT` | `token_endpoint` | OAuth token endpoint URL. Takes precedence over discovery. |
| `RC_CLIENT_ID` | `client_id` | OAuth client ID for the client-credentials grant. |
| `RC_CLIENT_SECRET` | Environment only | OAuth client secret for the client-credentials grant. TypeScript also accepts `client_secret` in the file and logs a warning. |


Each SDK finds the token endpoint from `RC_TOKEN_ENDPOINT` or, through discovery, from `RC_OIDC_ISSUER_URL`. Set one of them.

Each SDK chooses the OAuth grant from the credentials you set. When `RC_CLIENT_ID` and `RC_CLIENT_SECRET` are both set and not empty, the SDK uses the client-credentials grant. Otherwise, it uses the signed-challenge grant, with the client ID `customer_api`. Setting a client secret without a client ID is an error. In TypeScript and Python, a client ID without a secret is also an error, so don't set `client_id` for the signed-challenge grant.

For details about both grants, see [Authentication and signing](/pt-br/products/custody/developer-experience/sdks/authentication-and-signing).

### Signing and identity

| Variable | `config.toml` key | Purpose |
|  --- | --- | --- |
| `RC_PRIVATE_KEY_FILE` | `private_key_file` | Path to a PEM private key (P-256, secp256k1, or Ed25519). The SDK uses it to sign the login challenge and intents. |
| `RC_KMS_PROVIDER` | `kms_provider` | Sign with a key management service (KMS) instead of a key file: `hashicorp`, `aws`, or `azure`. The Java SDK has no built-in KMS signer. Use the Java examples instead, as described in [KMS provider variables](#kms-provider-variables). |
| `RC_USER_ID` | `rc_user_id` | Your user ID. The SDK uses it as the author of intents. |
| `RC_DOMAIN_ID` | `rc_domain_id` | Your domain ID. The SDK uses it as the author's domain and the default target domain. |


Set either a private key file or a KMS provider, not both. All three SDKs reject a configuration that sets both.

All three SDKs look up your user through the API when you don't set `RC_USER_ID` and `RC_DOMAIN_ID`. If you belong to several domains, set `RC_DOMAIN_ID` to choose one. Otherwise, the lookup fails.

### Verification and safety checks

| Variable | `config.toml` key | Purpose |
|  --- | --- | --- |
| `RC_NOTARY_API_PUBLIC_KEY` | `notary_api_public_key` | Notary API public key, base64-encoded DER. Required while response verification is on. |
| `RC_VERIFY_RESPONSES` | `verify_responses` | Verify the signature on API responses. Default `true`. |
| `RC_VALIDATE_ADDRESSES` | `rc_validate_addresses` | Check destination address formats before submitting an intent. Default `true`. |
| `RC_VERIFY_DERIVED_ADDRESSES` | `rc_verify_derived_addresses` | Re-derive generated unspent transaction output (UTXO) account addresses from the account's extended public key and compare them with the server's. Default `true`. |
| `RC_ENFORCE_IMMUTABLE_ENDPOINTS` | `rc_enforce_immutable_endpoints` | Default `true`: the SDK refuses an endpoint update that changes its address, ledger, or parameters. Set `false` to log a warning and submit the update instead. |


For more information about the endpoint check, see [SDK endpoint immutability check](/pt-br/products/custody/accounts-and-assets/endpoints/manage-endpoints#sdk-endpoint-immutability-check).

Each SDK pins one notary API public key. When the notary key rotates, replace the key and restart the client, as described in [Notary key rotation and migration](/pt-br/products/custody/operations-and-maintenance/security-maintenance/notary-key-rotation-and-migration#update-the-notary-key-pinned-in-sdk-clients). For how verification works, see [Response verification](/pt-br/products/custody/developer-experience/sdks/response-verification).

In TypeScript, the derived-address check runs only when you pass your own address-derivation function to the address methods. The Java SDK accepts the setting but doesn't perform the check yet.

### TLS

| Variable | `config.toml` key | Purpose |
|  --- | --- | --- |
| `RC_VERIFY_SSL` | `verify_ssl` | Verify the server's TLS certificate. Default `true`. |
| `RC_TLS_MIN_VERSION` | `tls_min_version` | Minimum TLS version: `TLSv1_2` (default) or `TLSv1_3`. |
| `RC_SSL_CA_CERT` | `ssl_ca_cert` | Path to a PEM CA bundle to trust instead of the system trust store. |
| `RC_CA_CERT_DATA` | `ca_cert_data` | CA bundle content, as an alternative to a file. |
| `RC_CERT_FILE` | `cert_file` | Client certificate (PEM) for mutual TLS. |
| `RC_KEY_FILE` | `key_file` | Client private key (PEM) for mutual TLS. |


- **Certificate verification.** Turn it off only for local development. All three SDKs log a warning when it's off.
- **Minimum version.** TLS 1.2 is the minimum in all three SDKs.
- **Custom CA.** In TypeScript and Python, set `RC_SSL_CA_CERT` or `RC_CA_CERT_DATA`, not both. The Java SDK accepts both together.
- **Mutual TLS.** Set `RC_CERT_FILE` and `RC_KEY_FILE` together. All three SDKs reject a configuration that sets only one.
- **Custom HTTP client.** If you supply your own HTTP client or transport, your client handles TLS, and these settings don't apply. For more information, see [Errors and HTTP behavior](/pt-br/products/custody/developer-experience/sdks/errors-and-http).


### Other settings

| Variable | `config.toml` key | Purpose |
|  --- | --- | --- |
| `RC_USER_AGENT_SUFFIX` | `user_agent_suffix` | Text appended to the SDK's `User-Agent` header, for example `my-app/1.0`. |
| `RC_LOG_LEVEL_NPM` | Environment only | TypeScript only. SDK log level: `error`, `warn`, `info`, `http`, `verbose`, `debug`, or `silly`. |
| `RC_ALLOW_INSECURE_HTTP` | Environment only | TypeScript only. Set to `true` or `1` to accept an `http://` API URL when the host isn't public. For local development only. |


Boolean values are `true` or `false`.

### KMS provider variables

When you set `RC_KMS_PROVIDER`, the TypeScript and Python SDKs read the provider's settings from environment variables only. The variable names differ between the two SDKs.

| Provider | TypeScript | Python |
|  --- | --- | --- |
| `hashicorp` | `VAULT_ADDR`, `VAULT_TOKEN`, `VAULT_TRANSIT_KEY_NAME`, `VAULT_NAMESPACE` | `VAULT_ADDR`, `VAULT_TOKEN` (or `VAULT_ROLE_ID` and `VAULT_SECRET_ID`), `VAULT_TRANSIT_KEY_NAME`, `VAULT_TRANSIT_MOUNT_POINT`, `VAULT_NAMESPACE` |
| `aws` | `RC_KMS_KEY_ID` (or `KMS_KEY_ID`), `AWS_REGION` | `AWS_KMS_KEY_ID`, `AWS_REGION`, `AWS_PROFILE` |
| `azure` | `AZURE_VAULT_URL`, `AZURE_KEY_NAME` | `AZURE_KEY_VAULT_URL`, `AZURE_KEY_NAME`, and optionally `AZURE_USE_MANAGED_IDENTITY` or the service principal variables `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET` |


The Java SDK recognizes `RC_KMS_PROVIDER` but has no built-in KMS signers, so it fails with an error that names the matching example. The Java examples show how to build each signer and read their own environment variables:

| Provider | Java example | Variables |
|  --- | --- | --- |
| `hashicorp` | `Example08HashiCorpVaultTransit` | `VAULT_ADDR`, `VAULT_TOKEN`, `VAULT_TRANSIT_KEY_NAME` |
| `azure` | `Example09AzureKeyVaultKeys` | `AZURE_KEY_VAULT_URL`, `AZURE_KEY_NAME`, and the tenant, client, and secret variables |
| `aws` | `Example10AwsKms` | `AWS_REGION`, `AWS_KMS_KEY_ID`, and the standard AWS credentials |


The examples aren't in the Java package. To get them, see [Examples](/pt-br/products/custody/developer-experience/sdks#examples). For key requirements and setup, see [Authentication and signing](/pt-br/products/custody/developer-experience/sdks/authentication-and-signing) and [HashiCorp Vault](/pt-br/products/custody/developer-experience/sdks/hashicorp-vault).

## Configure in code

You can build the configuration in code instead of loading it, for example when your application reads settings from its own secret store. The following examples sign with a local private key and set the same values as the quickstart configuration:

TypeScript
```typescript
import { readFileSync } from 'node:fs';
import {
  type Configuration,
  CustodyClient,
  generateNewConfiguration,
  makeSoftwareSigner,
} from '@ripple/ripple-custody-client-lib-typescript';

const apiBaseUrl = 'https://<api-host>';
const oidcIssuerUrl = 'https://<auth-host>/realms/<realm>';
const notaryApiPublicKey = '<notary-api-public-key>';
const signer = makeSoftwareSigner(readFileSync('/path/to/signing-key.pem', 'utf-8'));

const config: Configuration = {
  apiBaseUrl,
  oidcIssuerUrl,
  readPublicKey: signer.readPublicKey,
  signString: signer.signString,
  rcDomainId: '<your-domain-id>',
  rcValidateAddresses: true,
  rcVerifyDerivedAddresses: true,
  rcEnforceImmutableEndpoints: true,
  verifyResponses: true,
  notaryApiPublicKey,
  _sdkConfig: generateNewConfiguration({
    apiBaseUrl,
    oidcIssuerUrl,
    readPublicKey: signer.readPublicKey,
    signString: signer.signString,
    notaryApiPublicKey,
    // TLS options: verifySsl, tlsMinVersion, sslCaCert, caCertData, certFile, keyFile
  }),
};

const client = new CustodyClient(config);
```

`generateNewConfiguration` builds the HTTP and authentication layer. The outer object holds the settings that the client's methods use. Set `verifyResponses` and `notaryApiPublicKey` to the same values in both.

Java
```java
import com.ripple.custody.CustodyClient;
import com.ripple.custody.CustodyClientConfig;
import com.ripple.custody.auth.ChallengeSigner;
import com.ripple.custody.auth.SoftwarePemSigner;
import com.ripple.custody.invoker.ApiClient;
import com.ripple.custody.models.CoreUserReference;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;

ChallengeSigner signer =
    SoftwarePemSigner.fromPem(Files.readString(Path.of("/path/to/signing-key.pem")));

ApiClient apiClient = ApiClient.newBuilder()
    .tokenEndpoint("<token-endpoint-url>")
    .clientId("customer_api")
    .grantType("password")
    .signer(signer)
    // TLS options: .verifySsl(...), .sslCaCert(...), .caCertData(...), .certFile(...), .keyFile(...)
    .build();
apiClient.setBasePath("https://<api-host>");

UUID domainId = UUID.fromString("<your-domain-id>");
UUID userId = UUID.fromString("<your-user-id>");

CustodyClientConfig config = new CustodyClientConfig()
    .apiClient(apiClient)
    .signer(signer::signChallenge)
    .author(new CoreUserReference().id(userId).domainId(domainId))
    .defaultDomainId(domainId)
    .notaryApiPublicKey("<notary-api-public-key>");

CustodyClient client = new CustodyClient(config);
```

The same key signs the login challenge (`ApiClient`) and intents (`CustodyClientConfig.signer`).

You can also combine the two approaches: load a configuration with `CustodyClientConfig.loadConfiguration()`, adjust it with the setters, such as `notaryApiPublicKey(...)` or `defaultDomainId(...)`, and then call `CustodyClient.fromConfig(config)`.

Python
```python
from ripple_custody_client_lib_python import Configuration, CustodyClient

config = Configuration(
    host="https://<api-host>",
    oidc_issuer_url="https://<auth-host>/realms/<realm>",
    rc_user_id="<your-user-id>",
    rc_domain_id="<your-domain-id>",
    notary_api_public_key="<notary-api-public-key>",
    # TLS options: verify_ssl, tls_min_version, ssl_ca_cert, ca_cert_data, cert_file, key_file
)
config.sign_string = sign            # (message: str) -> base64-encoded signature
config.read_public_key = public_key  # () -> base64-encoded DER public key

client = CustodyClient(config)
```

`sign` and `public_key` are your own functions, backed by whatever holds your key. For ready-made signers, see [Authentication and signing](/pt-br/products/custody/developer-experience/sdks/authentication-and-signing).

Pass `notary_api_public_key` and `verify_responses` to the constructor. The SDK checks them together when it creates the object, so setting one afterward can fail.