Python SDK
Install clment-sdk, authenticate, and work through uploads, reviews and redlines with a client generated from the OpenAPI spec — including the urllib3 retry behaviour that will surprise you.
Updated 26 Aug 2026
clment-sdk is the official Python client for the Clment REST API. It is generated from the same OpenAPI document the API is built against, so it cannot quietly drift from the service — when an endpoint changes, the client changes with it.
It targets Python 3.10+ and builds on urllib3 and pydantic. Every request body and response is a validated pydantic model, which means a response that doesn’t match the documented shape raises at the boundary rather than surfacing as an AttributeError three functions later.
pip install clment-sdk
Your first call
import os
import clment_sdk
from clment_sdk.api.contracts_api import ContractsApi
configuration = clment_sdk.Configuration(
# Your organisation's region: us | eu | uk | au | nz | ca
host="https://api-nz.clment.com/v1",
)
configuration.access_token = os.environ["CLMENT_API_KEY"]
with clment_sdk.ApiClient(configuration) as client:
page = ContractsApi(client).list_contracts(status="active", take=20)
print(f"{len(page.data.contracts)} of {page.data.total} active contracts")
Use access_token, not an API-key helper — the client adds the Bearer prefix itself.
Get the region right
This is the one thing worth reading twice. Your contracts and your API keys live in exactly one region, and a valid key sent to the wrong region fails exactly like an invalid one — a 401 that sends people hunting for a new key when the host was the problem.
| Region | Base URL |
|---|---|
| United States | https://api-us.clment.com/v1 |
| European Union | https://api-eu.clment.com/v1 |
| United Kingdom | https://api-uk.clment.com/v1 |
| Australia | https://api-au.clment.com/v1 |
| New Zealand | https://api-nz.clment.com/v1 |
| Canada | https://api-ca.clment.com/v1 |
Your region is shown in Settings → API & Integrations. Note the api- prefix — a bare nz.clment.com does not resolve.
The retry behaviour that will surprise you
Read this before you put the client in a service.
clment-sdk retries 429 for you — three times, sleeping for whatever Retry-After says. That is urllib3’s default, inherited by every generated Python client, and it happens before the exception reaches your code. A Retry-After: 300 blocks your thread for fifteen minutes with no output and no log line.
That’s fine for a script. It is usually wrong inside a request handler or a worker that needs to shed load, switch queue, or raise an alert. To take the 429 yourself:
configuration.retries = 0
The TypeScript SDK does the opposite by default: it raises immediately and hands you retryAfterSeconds to act on. Neither is wrong; they just differ, and the difference is invisible until production.
Uploading a contract
Uploads are asynchronous — text extraction, classification and indexing run in the background. The call returns a job id; poll until it completes.
from clment_sdk.api.contracts_api import ContractsApi
contracts = ContractsApi(client)
result = contracts.upload_contract(
file="./acme-msa.pdf",
title="Acme MSA",
).data
if result.job_id:
while True:
job = contracts.get_upload_job(result.job_id).data
if job.status in ("complete", "completed", "succeeded"):
contract_id = job.contract_id
break
if job.status == "failed":
raise RuntimeError(f"upload failed: {job.error}")
time.sleep(2)
else:
# Small files can finish inline and return the contract directly.
contract_id = result.contract_id
Both success shapes share one response model, so job_id is always reachable — check it rather than assuming which path you got.
Uploads are not idempotent. The same file twice creates two contracts. Keep a record of what you have sent.
Running a review
A review needs a rulebook. List them, pick one, start the review, then poll the job — reviews run for minutes, so poll on a 10-second interval rather than a tight loop.
from clment_sdk.api.jobs_api import JobsApi
from clment_sdk.api.reviews_api import ReviewsApi
from clment_sdk.api.rulebooks_api import RulebooksApi
from clment_sdk.models.start_review_request import StartReviewRequest
rulebooks = RulebooksApi(client).list_rulebooks().data.rulebooks
reviews = ReviewsApi(client)
jobs = JobsApi(client)
started = reviews.start_review(
contract_id,
StartReviewRequest(rulebookId=rulebooks[0].id, mode="standard"),
).data
# Advisory only, but worth logging so a long wait looks intended.
print(f"~{round(started.estimate_seconds / 60)} min")
while True:
job = jobs.get_review_job(started.job_id).data
if job.status in ("complete", "completed", "succeeded"):
break
if job.status == "failed":
raise RuntimeError(f"review failed: {job.error}")
time.sleep(10)
review = reviews.get_review(job.review_id).data.review
print(f"{len(review.findings)} findings")
Comparing two versions is a review with scope="two_version", not a separate endpoint.
Recording verdicts and generating a redline
Findings carry a verdict you record, then a redline turns the accepted ones into a tracked-changes Word document.
from clment_sdk.models.generate_redline_request import GenerateRedlineRequest
started = reviews.generate_redline(
contract_id,
GenerateRedlineRequest(
playbookName=rulebooks[0].name,
sourceReviewId=review.id,
findings=review.findings[:5],
),
).data
while True:
job = jobs.get_redline_job(started.job_id).data
if job.status in ("complete", "completed", "succeeded"):
break
time.sleep(5)
A failed redline can report complete and then 404 on download — check job.outcome for the explanation rather than treating the 404 as a bug.
Errors
Every non-2xx raises a subclass of ApiException, so you branch on a type rather than a status number.
from clment_sdk.exceptions import (
ApiException,
ForbiddenException,
NotFoundException,
ServiceException,
UnauthorizedException,
)
try:
contracts.list_contracts()
except UnauthorizedException:
# Almost always the wrong region, not a bad key.
raise
except NotFoundException:
...
except ApiException as err:
print(err.status, err.reason, err.body)
| Class | When |
|---|---|
UnauthorizedException | 401 — usually the wrong region |
ForbiddenException | 403 — plan doesn’t include the API, or the key can’t reach settings |
NotFoundException | 404 |
ConflictException | 409 |
UnprocessableEntityException | 422 |
ServiceException | 5xx |
ApiException | everything else — carries status, reason, body, headers |
Retry the transient ones — 429, 5xx and connection failures. A 401, 403 or 404 will not fix itself.
Rate limits and credits
Two independent limits apply.
Credits. AI operations — reviews, redlines, conversions — consume credits from your plan’s monthly allowance, then any credit packs. Reads are free. An exhausted balance returns 402 INSUFFICIENT_CREDITS. Check UsageApi.get_usage() before a batch rather than discovering exhaustion halfway through one.
Request rate. Requests are limited per organisation on a rolling hourly window sized to your plan. Every authenticated response carries X-RateLimit-Limit and X-RateLimit-Remaining; a 401 carries neither, because the limiter runs only once the key resolves to an organisation.
Pagination
List endpoints page with skip and take (max 100), and return a total:
skip, page_size = 0, 100
while True:
page = contracts.list_contracts(skip=skip, take=page_size).data
for contract in page.contracts:
...
skip += page_size
if skip >= page.total:
break
Names the generator had to escape
Where a field name would collide with something in Python, the generator renames it rather than dropping it. A key date’s date arrives as var_date. If an attribute you expect is missing, look for a var_ prefix before assuming the field isn’t there.
What else is there
The client covers the whole documented API — contracts, reviews, rulebooks, key dates, tags, search, insights, webhooks, conversions and jobs. Each group is its own *Api class under clment_sdk.api. Browse the API reference for the full surface, or read Workflow examples for end-to-end recipes.
Choosing between the SDKs
- TypeScript — hand-written over generated types. Friendlier surface,
waitForhelpers, region as a typed argument. - Python (this page) and .NET — generated. Thinner, but exhaustive and guaranteed in step with the API.
All three are built from the same OpenAPI document, which you can also generate a client from in any other language — see Developers.