This is a typed Python client for the OpenClear API. It's for banking cores, online banking providers and a bank's own systems that send items to OpenClear and act on its pay and return decisions.
It's licensed Apache-2.0, so you can ship it inside proprietary software. OpenClear itself is AGPL-3.0.
pip install openclear-sdkIt requires Python 3.11 or later and has one dependency, httpx.
To connect, you need an API client. A bank admin creates one under Admin → API clients, chooses a security group (what the client may do) and, optionally, a customer (whose data it may see). The admin then gives you the client id and secret. The secret is shown only once.
from openclear_sdk import OpenClear
oc = OpenClear("https://openclear.yourbank.example", client_id="occ_...", client_secret="...")The client fetches the access token itself and renews it before it expires.
Other ways to authenticate:
- To use a token you already have, pass
access_token=.... - For the platform endpoints (
platform_*), passplatform_api_key=....
Each API area is an attribute of the client, with one method per endpoint. The methods are generated from OpenClear's published OpenAPI contract, and their arguments and results are typed with TypedDict.
oc.customers.upsert_customer("CIF-1001", {"name": "Acme Corp"})
oc.accounts.upsert_account("12345678", {"name": "Operating", "customer_number": "CIF-1001",
"custom_fields": {"aggregation_id": "G-42"}})
item = oc.issued_items.create_issued_item({
"account_id": account_id, "check_number": "1001", "amount": "250.00",
"payee_name": "Jane Doe", "issue_date": "2026-10-01",
})Lists. Every list endpoint has a list_... method, which returns one page, and an iter_... method, which follows the pages for you:
for item in oc.issued_items.iter_issued_items(status_filter="issued"):
...Retries.
- Every write carries an
Idempotency-Key, and the same key is reused on every retry, so a retried request never acts twice. - Connection failures and responses 429, 502, 503 and 504 are retried with backoff. Set
max_retries(default 3) to change how many times.
Errors. They're typed and all inherit from OpenClearError:
| Error | When |
|---|---|
AuthenticationError |
401 |
PermissionDeniedError |
403 |
NotFoundError |
404 |
ConflictError |
409 |
ValidationError |
422 (.errors lists the field problems) |
RateLimitError |
429 (.retry_after) |
ServerError |
5xx |
APIConnectionError |
no response |
A bank's own fields on customers and accounts come and go as custom_fields, a map from the field's key to a string: an ISO date (2026-10-01), decimal text (12.5), true/false, or a choice's option value. GET /api/v1/custom-fields gives each field's type; convert on your side. In a PUT, keys you leave out keep their values, and null clears one; a required field is required in people's forms only, so a sync written before the field existed keeps working.
result = oc.bulk.submit("issued_items", items) # also "paid_items", "ach_transactions"
print(result.succeeded, result.failed)
for failure in result.failures:
print(failure["index"], failure["error"])submit works like this:
- It splits the list into batches.
- If a batch is too large for OpenClear to import during the call, OpenClear answers
202and imports it in the background.submitwaits for that import to finish. - It returns one result per item, in the order you sent them.
To send a file in one of your core's formats, use upload_file. It works for OpenClear's own formats and for formats a plugin adds, once the bank has turned them on:
oc.bulk.upload_file("paid_items", "tabular", open("presented.csv", "rb"))The decision feed lists every pay and return decision in order. The usual core job reads the new decisions, acts on each one, and acknowledges how far it got:
def handle(decision):
if decision["outcome"] == "return":
core.return_item(decision["account_number"], decision["check_number"] or decision["trace_number"])
else:
core.pay_item(...)
oc.decision_feed.consume("core", handle) # run every few minutesIf handle raises an error, the decisions after the last acknowledged page are delivered again on the next run. Make handle safe to run twice for the same decision.
To get a file instead, use oc.decision_feed.decision_file(layout="openclear_csv", after=n). It also returns layouts that a plugin adds.
Verify each webhook before acting on it. Pass the raw request body:
from openclear_sdk import webhooks, WebhookVerificationError
event = webhooks.verify(secret, request.headers, request.body) # raises WebhookVerificationError
if event["type"] == "exception.created":
exception = oc.exceptions.get_exception(event["data"]["exception_id"])Webhook payloads only carry ids, so fetch the details through the API. A retried webhook keeps its webhook-id, so you can use that header to skip events you've already handled.
If the bank has allowed your client to act for its people, you can make calls on a person's behalf:
as_jane = oc.acting_as("jane@acme.example", customer_number="CIF-1001")
as_jane.decisions.decide_exception(exception_id, {"outcome": "pay"})The call can only do what both your client and that person are allowed to do. The bank's action log records the action under the person's name and notes that your client made it.
If the bank signs its business customers' people in through your platform (an OIDC sign-on connection with a customer claim), and links your API client to that connection, you have two options:
- Exchange the person's ID token. You get a client that acts for that person within their own customer. The person is created in OpenClear on first use.
- Name the person by your own id for them. This works once they've signed in or been exchanged at least once.
jo = oc.for_person(id_token_from_your_sign_in) # token exchange: acts for the signed-in person
jo.exceptions.list_exceptions()
oc.acting_as_subject("your-user-id-for-them") # or name them by your own id for themThe package installs an openclear command for scripts and cron jobs. It reads its credentials from OPENCLEAR_URL, OPENCLEAR_CLIENT_ID and OPENCLEAR_CLIENT_SECRET.
openclear push paid_items presented.csv # import a file, list the rows that failed
openclear decisions pull --consumer core --format csv # new decisions, then acknowledge them
openclear decisions files --download ./outbox # outbox files, SHA-256 checked, then acknowledged
openclear webhooks tail --secret whsec_... # receive webhooks locally, verify and print them
openclear compliance attestations --start 2026-01-01 --end 2026-03-31 --format oscal # a quarter's attestationsExit codes:
| Code | Meaning |
|---|---|
| 0 | Done |
| 1 | Some rows failed, or a file didn't verify |
| 2 | Bad usage or credentials |
openclear_sdk.API_VERSION is the OpenClear API version the SDK was generated from. /api/v1 only changes in ways that add to it, so newer servers keep working with this SDK.