{"templateId":"markdown","versions":[{"version":"v1.43","label":"v1.43 LTS","link":"/products/custody/developer-experience/sdks/errors-and-http","default":true,"active":true,"folderId":"c15a2701"},{"version":"v1.42","label":"v1.42 STS","link":"/products/custody/v1.42/developer-experience/sdks/errors-and-http","default":false,"active":false,"folderId":"c15a2701"},{"version":"v1.41","label":"v1.41 STS","link":"/products/custody/v1.41/developer-experience/sdks/errors-and-http","default":false,"active":false,"folderId":"c15a2701"},{"version":"v1.34","label":"v1.34 LTS","link":"/products/custody/v1.34/developer-experience/sdks/errors-and-http","default":false,"active":false,"folderId":"c15a2701"},{"version":"v1.26","label":"v1.26 LTS","link":"/products/custody/v1.26/developer-experience/sdks/errors-and-http","default":false,"active":false,"folderId":"c15a2701"},{"version":"v1.19","label":"v1.19 LTS","link":"/products/custody/v1.19/developer-experience/sdks/errors-and-http","default":false,"active":false,"folderId":"c15a2701"},{"version":"v1.15","label":"v1.15 LTS","link":"/products/custody/v1.15/developer-experience/sdks/errors-and-http","default":false,"active":false,"folderId":"c15a2701"}],"sharedDataIds":{"sidebar":"sidebar-products/custody/@v1.15/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["tabs","tab"]},"type":"markdown"},"seo":{"title":"Errors, pagination, and HTTP settings","description":"User guides, API reference, and support resources.","siteUrl":"https://docs.ripple.com","lang":"en-US"},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"errors-pagination-and-http-settings","__idx":0},"children":["Errors, pagination, and HTTP settings"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This page describes how the SDKs report errors and how to page through list results. It also covers the HTTP transport settings: retries, timeouts, TLS, proxies, a custom HTTP client, and logging."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK examples assume a configured client. To set one up, see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/custody/get-started/integrate-via-sdk"},"children":["Integrate via SDK"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"error-hierarchy","__idx":1},"children":["Error hierarchy"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Every error that the client raises derives from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CustodyError"]},", so one ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["catch (CustodyError)"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["except CustodyError"]}," handles any SDK failure. Each SDK represents HTTP errors differently."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"TypeScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"CustodyError                      statusCode: HTTP status, or 0 if no response was received\n├── ApiError                      status, reason, body, data (parsed JSON body)\n│   ├── BadRequestError           400\n│   ├── UnauthorizedError         401\n│   ├── ForbiddenError            403\n│   ├── NotFoundError             404\n│   ├── ConflictError             409\n│   ├── UnprocessableEntityError  422\n│   └── ServiceError              5xx\n├── NetworkError                  DNS, connection, TLS, or timeout failure; cause holds the original error\n├── ConfigurationError\n├── MissingConfigurationError\n├── IntentTimeoutError            lastState\n├── ResponseSignatureError\n└── AddressIntegrityError\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK raises other 4xx statuses as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ApiError"]},". It doesn't throw ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CustodyWarning"]},". Instead, it passes the warning message to the logger."]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Java","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"RuntimeException\n├── CustodyError\n│   ├── IntentTimeoutError        getLastState()\n│   ├── ResponseSignatureError\n│   └── AddressIntegrityError\n└── CustodyWarning\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Every SDK error is an unchecked exception. Client methods wrap HTTP failures in a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CustodyError"]}," whose cause is the generated ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ApiException"]},". Read the status with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["getCode()"]}," and the body with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["getResponseBody()"]},". ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["getCode()"]}," returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["0"]}," when the SDK received no HTTP response, for example on a connection failure."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you call the generated ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["com.ripple.custody.api.*"]}," classes directly, they throw the checked ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ApiException"]}," without wrapping it."]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"CustodyError                         status_code: HTTP status, or 0 if no response was received\n├── HTTP errors                      status, reason, body, data, headers\n│   ├── CustodyBadRequestError           400\n│   ├── CustodyUnauthorizedError         401\n│   ├── CustodyForbiddenError            403\n│   ├── CustodyNotFoundError             404\n│   ├── CustodyConflictError             409\n│   ├── CustodyUnprocessableEntityError  422\n│   ├── CustodyServiceError              5xx\n│   └── CustodyApiError                  any other status\n├── ConfigurationError               also a ValueError\n│   └── MissingConfigurationError\n├── IntentTimeoutError               last_state\n├── ResponseSignatureError\n└── AddressIntegrityError\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each HTTP error class also inherits the matching generated exception, such as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["NotFoundException"]},", so existing ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["except ApiException"]}," handlers keep working. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CustodyApiError"]}," is a sibling of the status-specific classes, not their parent. To catch every HTTP error, catch ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CustodyError"]}," and check ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status_code"]},". The status-specific classes are in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ripple_custody_client_lib_python.custody.exceptions"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK raises a TLS failure as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CustodyApiError"]}," with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status_code"]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["0"]},". For other connection failures and timeouts, urllib3 raises its own exceptions, for example ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["urllib3.exceptions.MaxRetryError"]},". These exceptions aren't ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CustodyError"]}," subclasses."]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Branch on the status code or the error class, never on the message text."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A failed intent isn't an HTTP error. Submitting an intent returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["202 Accepted"]},", and the platform processes it afterward. If processing fails, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["waitAccepted"]}," throws a plain ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CustodyError"]}," with status code ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["0"]},". See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/custody/developer-experience/sdks/intents"},"children":["Work with intents"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"catch-not-found-and-conflict-errors","__idx":2},"children":["Catch not-found and conflict errors"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The following examples handle ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["404"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["409"]}," responses when you read a user:"]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"TypeScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"import { ConflictError, NotFoundError } from '@ripple/ripple-custody-client-lib-typescript';\n\ntry {\n  const user = await client.governance.users.get(userId);\n} catch (err) {\n  if (err instanceof NotFoundError) {\n    // The user doesn't exist, or isn't visible to you\n  } else if (err instanceof ConflictError) {\n    // For example, a duplicate ID or a revision mismatch\n  } else {\n    throw err;\n  }\n}\n","lang":"typescript"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Java","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","header":{"controls":{"copy":{}}},"source":"// Returns Optional.empty() on 404 instead of throwing\nOptional<CoreTrustedUser> user = client.governance().users().find(domainId, userId);\n\ntry {\n    client.governance().users().get(domainId, userId);\n} catch (CustodyError e) {\n    int status = e.getCause() instanceof ApiException api ? api.getCode() : 0;\n    if (status == 404) {\n        // The user doesn't exist, or isn't visible to you\n    } else if (status == 409) {\n        // For example, a duplicate ID or a revision mismatch\n    } else {\n        throw e;\n    }\n}\n","lang":"java"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"from ripple_custody_client_lib_python import CustodyError\n\ntry:\n    user = client.governance.users.get(user_id)\nexcept CustodyError as exc:\n    if exc.status_code == 404:\n        pass  # The user doesn't exist, or isn't visible to you\n    elif exc.status_code == 409:\n        pass  # For example, a duplicate ID or a revision mismatch\n    else:\n        raise\n","lang":"python"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"pagination","__idx":3},"children":["Pagination"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["List methods return one page by default. A page has ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["items"]}," and a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["nextStartingAfter"]}," cursor. To get the next page, pass the cursor as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["startingAfter"]},". When the cursor is empty, there are no more pages."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each SDK also has a way to read all pages:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["TypeScript:"]}," pass ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["autoPaginate: true"]},". The method returns an array of every item instead of a page. It stops with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CustodyError"]}," after 1,000 pages."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Java:"]}," call ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stream(...)"]}," (or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["streamOrders"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["streamTransactions"]},", and similar). The stream fetches pages lazily as you consume it. For list methods without a stream variant, build one with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["AutoPaginate.stream(...)"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Python:"]}," pass ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["auto_paginate=True"]},". The method returns a list of every item instead of a page."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In TypeScript and Python, automatic pagination loads every item into memory before it returns. For large result sets, page manually."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The following examples show both approaches:"]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"TypeScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"import type {\n  CoreTrustedDomain,\n  CoreTrustedDomainsCollection,\n} from '@ripple/ripple-custody-client-lib-typescript';\n\n// One page at a time\nlet cursor: string | undefined;\ndo {\n  const page = (await client.governance.domains.list({\n    limit: 50,\n    startingAfter: cursor,\n  })) as CoreTrustedDomainsCollection;\n  for (const domain of page.items) console.log(domain.data.id);\n  cursor = page.nextStartingAfter || undefined;\n} while (cursor);\n\n// Every page at once\nconst all = (await client.governance.domains.list({\n  autoPaginate: true,\n})) as CoreTrustedDomain[];\n","lang":"typescript"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Java","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","header":{"controls":{"copy":{}}},"source":"// One page at a time\nUUID cursor = null;\ndo {\n    CoreTrustedDomainsCollection page = client.governance().domains().list(50, cursor);\n    page.getItems().forEach(domain -> System.out.println(domain.getData().getId()));\n    String next = page.getNextStartingAfter();\n    cursor = next == null || next.isBlank() ? null : UUID.fromString(next);\n} while (cursor != null);\n\n// Every page, fetched lazily\nclient.governance().domains().stream()\n    .forEach(domain -> System.out.println(domain.getData().getId()));\n","lang":"java"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"# One page at a time\ncursor = None\nwhile True:\n    page = client.governance.domains.list(limit=50, starting_after=cursor)\n    for domain in page.items or []:\n        print(domain.data.id)\n    cursor = page.next_starting_after\n    if not cursor:\n        break\n\n# Every page at once\nall_domains = client.governance.domains.list(auto_paginate=True)\n","lang":"python"},"children":[]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For a complete program, see example ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["01"]}," (listing and pagination). To get the examples, see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/custody/developer-experience/sdks#examples"},"children":["Examples"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"inject-a-custom-http-client","__idx":4},"children":["Inject a custom HTTP client"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each SDK lets you supply your own HTTP transport. Use this to add retries, timeouts, a proxy, tracing headers, or request logging. When you supply a transport, it owns the connection. The SDK's TLS settings (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RC_VERIFY_SSL"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RC_TLS_MIN_VERSION"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RC_SSL_CA_CERT"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RC_CA_CERT_DATA"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RC_CERT_FILE"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RC_KEY_FILE"]},") no longer apply, so configure TLS on your transport. Response-signature verification still applies."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"TypeScript","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Pass a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetchApi"]}," function to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["generateNewConfiguration"]}," and use the result as the client's transport:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"import {\n  CustodyClient,\n  generateNewConfiguration,\n  loadConfiguration,\n} from '@ripple/ripple-custody-client-lib-typescript';\n\nconst config = loadConfiguration();\n\nconst myFetch: typeof fetch = async (url, init) => fetch(url, init);\n\nconst sdkConfig = generateNewConfiguration({\n  apiBaseUrl: config.apiBaseUrl,\n  oidcIssuerUrl: config.oidcIssuerUrl,\n  tokenEndpoint: config.tokenEndpoint,\n  readPublicKey: config.readPublicKey,\n  signString: config.signString,\n  verifyResponses: config.verifyResponses,\n  notaryApiPublicKey: config.notaryApiPublicKey,\n  fetchApi: myFetch,\n});\n\nconst client = new CustodyClient({ ...config, _sdkConfig: sdkConfig });\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["generateNewConfiguration"]}," doesn't read ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["config.toml"]},", so pass it the values that ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["loadConfiguration()"]}," returns, as shown. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["loadConfiguration()"]}," doesn't return the client ID or client secret. For the client-credentials grant, also pass ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["oAuthServerClientId"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["oAuthServerClientSecret"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["OAuth token requests use Node's global ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetch"]},", not ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetchApi"]},". A custom transport, including any proxy you configure on it, doesn't apply to them."]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Java","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Pass an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OkHttpClient"]}," to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ApiClient.newBuilder().httpClient(...)"]},". ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CustodyClient.fromConfig"]}," has no hook for this, so build the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ApiClient"]}," yourself. Start from the SDK's hardened default client to keep its TLS 1.2 minimum:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","header":{"controls":{"copy":{}}},"source":"import com.ripple.custody.CustodyClientConfig;\nimport com.ripple.custody.auth.SignerFactory;\nimport com.ripple.custody.invoker.ApiClient;\nimport com.ripple.custody.transport.OkHttpClientAdapter;\nimport java.util.concurrent.TimeUnit;\nimport okhttp3.OkHttpClient;\n\nOkHttpClient httpClient = OkHttpClientAdapter.defaultHardenedClient().newBuilder()\n    .retryOnConnectionFailure(false)\n    .connectTimeout(10, TimeUnit.SECONDS)\n    .readTimeout(30, TimeUnit.SECONDS)\n    .build();\n\nCustodyClientConfig config = CustodyClientConfig.loadConfiguration();\nSignerFactory.Signers signers = SignerFactory.fromConfig(config);\n\nApiClient apiClient = ApiClient.newBuilder()\n    .tokenEndpoint(config.getEffectiveTokenEndpoint())\n    .clientId(config.getClientId() != null ? config.getClientId() : \"customer_api\")\n    .grantType(\"password\")\n    .signer(signers.getChallengeSigner())\n    .httpClient(httpClient)\n    .build();\napiClient.setBasePath(config.getApiBaseUrl());\nconfig.apiClient(apiClient).signer(signers.getIntentSigner());\n\nCustodyClient client = new CustodyClient(config);\n","lang":"java"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The injected client also sends OAuth token requests. This example disables OkHttp's automatic retries after connection failures. See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#retries"},"children":["Retries"]}," for the default behavior."]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Pass a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["urllib3.PoolManager"]}," (or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ProxyManager"]},") as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pool_manager"]},". Set the TLS minimum on it yourself. The SDK warns if an injected pool accepts a lower TLS version than its configured minimum or disables certificate verification. Set the request timeout on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Configuration"]},", not just on the pool. The following example sets a 30-second request timeout, disables urllib3 retries, and sets a TLS 1.2 minimum:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import ssl\n\nimport urllib3\nfrom ripple_custody_client_lib_python import CustodyClient, load_configuration\n\nconfig = load_configuration()\nconfig.timeout = 30.0\npool = urllib3.PoolManager(\n    retries=False,\n    ssl_minimum_version=ssl.TLSVersion.TLSv1_2,\n)\n\ntry:\n    with CustodyClient(config, pool_manager=pool) as client:\n        ...\nfinally:\n    pool.clear()\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK passes a timeout on each API request, overriding the pool's default. If you leave ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["config.timeout"]}," unset, the SDK passes ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["None"]},". Setting a timeout only on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PoolManager"]}," doesn't protect those requests. The SDK doesn't close an injected pool, so the example clears it in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["finally"]},". OAuth token requests don't use the pool. They use the timeout, proxy, and TLS settings on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Configuration"]},"."]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"retries","__idx":5},"children":["Retries"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK or its HTTP transport can retry a request. The defaults differ by language:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"SDK"},"children":["SDK"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Default behavior"},"children":["Default behavior"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["TypeScript"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Doesn't retry. A ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["401"]}," response throws ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["UnauthorizedError"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Java"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Uses OkHttp's automatic retries after connection failures."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Python (synchronous client)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Uses urllib3's default retry policy."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The language tabs below explain how to configure retries. The Python settings apply to the synchronous ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CustodyClient"]}," and urllib3, not an injected asynchronous transport. OAuth token requests can use a different transport, as described in ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#inject-a-custom-http-client"},"children":["Inject a custom HTTP client"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you add retries for temporary failures, restrict them to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET"]}," requests. Account for any retries that the HTTP transport already makes. Don't automatically retry ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST"]}," requests that submit, approve, or reject intents. ","Ripple Custody"," might already have accepted a request that timed out. Before you resubmit after an unclear failure, list recent intents and check whether the first one arrived."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The following examples retry only ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET"]}," requests that fail with a transient status:"]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"TypeScript","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK doesn't retry requests, including after a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["401"]}," response. To retry, add retries through a custom ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetchApi"]},". The example below adds retries for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["429"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["5xx"]}," responses on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET"]}," requests."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"const retryingFetch: typeof fetch = async (url, init) => {\n  const method = init?.method ?? 'GET';\n  for (let attempt = 1; ; attempt++) {\n    const response = await fetch(url, init);\n    const transient = response.status === 429 || response.status >= 500;\n    if (method !== 'GET' || !transient || attempt === 3) return response;\n    await new Promise((resolve) => setTimeout(resolve, 500 * 2 ** attempt));\n  }\n};\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Pass ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["retryingFetch"]}," as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetchApi"]},", as shown in ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#inject-a-custom-http-client"},"children":["Inject a custom HTTP client"]},"."]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Java","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["OkHttp can retry requests after connection failures, including non-",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET"]}," requests. It doesn't retry every HTTP error status. Set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".retryOnConnectionFailure(false)"]}," on an injected client to disable connection retries. Also review any interceptors and OkHttp settings that can send extra requests, such as redirects."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","header":{"controls":{"copy":{}}},"source":"import okhttp3.Interceptor;\nimport okhttp3.Request;\nimport okhttp3.Response;\n\nInterceptor retryReads = chain -> {\n    Request request = chain.request();\n    Response response = chain.proceed(request);\n    for (int attempt = 1; attempt < 3 && \"GET\".equals(request.method())\n            && (response.code() == 429 || response.code() >= 500); attempt++) {\n        response.close();\n        response = chain.proceed(request);\n    }\n    return response;\n};\n","lang":"java"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Add the interceptor with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".addInterceptor(retryReads)"]}," to the injected ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OkHttpClient"]}," above. Keep ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".retryOnConnectionFailure(false)"]}," so connection retries don't bypass this ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET"]},"-only policy."]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["config.retries"]}," defaults to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["None"]},", which leaves urllib3's default retry policy in place. urllib3 can retry requests after connection failures, including non-",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET"]}," requests. Its policy also controls retries after read errors and HTTP error responses."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For a pool that the SDK creates, set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["config.retries = 0"]}," to disable retries. For an injected pool, set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["retries=False"]}," or supply a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["urllib3.Retry"]}," policy on the pool. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["config.retries"]}," doesn't configure an injected pool. The following policy retries only ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET"]}," requests with the listed status codes:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import ssl\n\nimport urllib3\nfrom ripple_custody_client_lib_python import CustodyClient, load_configuration\n\nconfig = load_configuration()\nretry = urllib3.Retry(\n    total=3,\n    connect=0,\n    read=0,\n    other=0,\n    redirect=0,\n    backoff_factor=0.5,\n    status_forcelist={429, 500, 502, 503, 504},\n    allowed_methods={\"GET\"},\n    raise_on_status=False,\n)\npool = urllib3.PoolManager(\n    retries=retry,\n    ssl_minimum_version=ssl.TLSVersion.TLSv1_2,\n)\nclient = CustodyClient(config, pool_manager=pool)\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["allowed_methods"]}," alone doesn't prevent connection retries for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST"]},". This policy also disables retries after connection failures, read errors, other errors, and redirects. It retries only ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET"]}," requests with the specified status codes."]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"timeouts","__idx":6},"children":["Timeouts"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["HTTP timeouts limit a single request. They're separate from the timeouts you pass to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["waitAccepted"]}," and the other intent wait methods, which limit how long the SDK keeps polling. See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/custody/developer-experience/sdks/intents#timeouts-and-intenttimeouterror"},"children":["Work with intents"]},"."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["TypeScript:"]}," the built-in transport sets no request timeout of its own. Token requests time out after 30 seconds. A request that times out throws ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["NetworkError"]},". To limit API requests, use a custom ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetchApi"]}," that sets an abort signal:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"const fetchWithTimeout: typeof fetch = (url, init) =>\n  fetch(url, { ...init, signal: AbortSignal.timeout(30_000) });\n","lang":"typescript"},"children":[]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Java:"]}," the default client uses the OkHttp defaults of 10 seconds for connect, read, and write. To change them, set them on a custom ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OkHttpClient"]},", as shown in ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#inject-a-custom-http-client"},"children":["Inject a custom HTTP client"]},"."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Python:"]}," set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["config.timeout"]}," to a number of seconds before you create the client. It applies to API requests and token requests, even when you inject a custom pool. A timeout set only on the pool doesn't protect API requests because the SDK overrides it."]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"tls-settings","__idx":7},"children":["TLS settings"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDKs verify the server certificate by default. The TypeScript SDK also rejects an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["http://"]}," API base URL unless the host is local. You configure the following settings in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["config.toml"]}," or with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RC_*"]}," environment variables. For the key names and formats, see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/custody/developer-experience/sdks/configuration"},"children":["Configure the SDK"]},"."]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Setting"},"children":["Setting"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Environment variable"},"children":["Environment variable"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Notes"},"children":["Notes"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Minimum TLS version"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RC_TLS_MIN_VERSION"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["TypeScript and Python. Default ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["TLSv1_2"]},". Set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["TLSv1_3"]}," to require TLS 1.3. The Java SDK always accepts TLS 1.2 and 1.3 and has no setting."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Custom CA bundle"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RC_SSL_CA_CERT"]}," (file path) or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RC_CA_CERT_DATA"]}," (inline PEM)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Use when a private certificate authority (CA) issues your deployment's certificate."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Mutual TLS"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RC_CERT_FILE"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RC_KEY_FILE"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Set both or neither."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Certificate verification"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RC_VERIFY_SSL"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Default ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["true"]},". Set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["false"]}," only for local development."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["These settings apply only to the transport the SDK builds. If you inject your own HTTP client, configure TLS on it."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"proxy","__idx":8},"children":["Proxy"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["How you route requests through a proxy differs by SDK."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"TypeScript","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK has no proxy setting. Install ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["undici"]}," in your project, and route API requests through a proxy with a custom ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetchApi"]}," that uses undici's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ProxyAgent"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"import { ProxyAgent, fetch as undiciFetch } from 'undici';\n\nconst proxy = new ProxyAgent('http://proxy.example.com:3128');\nconst proxiedFetch: typeof fetch = (input, init) =>\n  undiciFetch(input as Parameters<typeof undiciFetch>[0], {\n    ...(init as Parameters<typeof undiciFetch>[1]),\n    dispatcher: proxy,\n  }) as unknown as Promise<Response>;\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Pass ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["proxiedFetch"]}," as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetchApi"]},". Token requests don't go through ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetchApi"]},", so this doesn't proxy them."]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Java","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Set the proxy on a custom ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OkHttpClient"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","header":{"controls":{"copy":{}}},"source":"import com.ripple.custody.transport.OkHttpClientAdapter;\nimport java.net.InetSocketAddress;\nimport java.net.Proxy;\nimport okhttp3.OkHttpClient;\n\nOkHttpClient httpClient = OkHttpClientAdapter.defaultHardenedClient().newBuilder()\n    .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(\"proxy.example.com\", 3128)))\n    .build();\n","lang":"java"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Pass it to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ApiClient.newBuilder().httpClient(...)"]},", as shown in ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#inject-a-custom-http-client"},"children":["Inject a custom HTTP client"]},". Token requests use the same client."]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The proxy setting applies to API requests and token requests. The SDK also supports SOCKS proxy URLs, such as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["socks5://"]},". Set the proxy on the configuration before you create the client:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"from ripple_custody_client_lib_python import CustodyClient, load_configuration\n\nconfig = load_configuration()\nconfig.proxy = \"http://proxy.example.com:3128\"\n\nclient = CustodyClient(config)\n","lang":"python"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"logging","__idx":9},"children":["Logging"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each SDK handles logging differently."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"TypeScript","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK logs nothing by default. Set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RC_LOG_LEVEL_NPM"]}," (for example, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["warn"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["debug"]},") to log to the console, or route SDK logs to your own logger with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setLogger"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"import { setLogger } from '@ripple/ripple-custody-client-lib-typescript';\n\nsetLogger({\n  debug: () => {},\n  info: (msg) => console.info(msg),\n  warn: (msg) => console.warn(msg),\n  error: (msg, err) => console.error(msg, err),\n});\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Call ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["setLogger"]}," before you create the client."]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Java","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK writes its warnings, such as a warning that certificate verification is off, through ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["java.util.logging"]}," (JUL) under the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["com.ripple.custody"]}," logger names. Configure JUL the same way you do for the rest of your application."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To log HTTP traffic, add OkHttp's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["HttpLoggingInterceptor"]}," to a custom ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OkHttpClient"]},". Use the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BASIC"]}," level in production: the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["HEADERS"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BODY"]}," levels write access tokens and payloads to the log."]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK logs through the standard ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["logging"]}," module under the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ripple_custody_client_lib_python"]}," logger, which has only a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["NullHandler"]}," by default. HTTP-level messages come from the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["urllib3"]}," logger. For example, to log SDK messages at ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["INFO"]}," level:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import logging\n\nlogging.basicConfig(level=logging.WARNING)\nlogging.getLogger(\"ripple_custody_client_lib_python\").setLevel(logging.INFO)\n","lang":"python"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"related-topics","__idx":10},"children":["Related topics"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/custody/developer-experience/sdks/configuration"},"children":["Configure the SDK"]},": configuration keys and environment variables, including the TLS settings."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/custody/developer-experience/sdks/intents"},"children":["Work with intents"]},": submit intents and wait for their outcome with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PendingIntent"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/products/custody/developer-experience/sdks/response-verification"},"children":["Verify response signatures"]},": pin the notary API public key and handle ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ResponseSignatureError"]},"."]}]}]},"headings":[{"value":"Errors, pagination, and HTTP settings","id":"errors-pagination-and-http-settings","depth":1},{"value":"Error hierarchy","id":"error-hierarchy","depth":2},{"value":"Catch not-found and conflict errors","id":"catch-not-found-and-conflict-errors","depth":2},{"value":"Pagination","id":"pagination","depth":2},{"value":"Inject a custom HTTP client","id":"inject-a-custom-http-client","depth":2},{"value":"Retries","id":"retries","depth":2},{"value":"Timeouts","id":"timeouts","depth":2},{"value":"TLS settings","id":"tls-settings","depth":2},{"value":"Proxy","id":"proxy","depth":2},{"value":"Logging","id":"logging","depth":2},{"value":"Related topics","id":"related-topics","depth":2}],"frontmatter":{"seo":{"title":"Errors, pagination, and HTTP settings"}},"lastModified":"2026-10-23T09:06:20.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/products/custody/developer-experience/sdks/errors-and-http","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}