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.

RegionBase URL
United Stateshttps://api-us.clment.com/v1
European Unionhttps://api-eu.clment.com/v1
United Kingdomhttps://api-uk.clment.com/v1
Australiahttps://api-au.clment.com/v1
New Zealandhttps://api-nz.clment.com/v1
Canadahttps://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)
ClassWhen
UnauthorizedException401 — usually the wrong region
ForbiddenException403 — plan doesn’t include the API, or the key can’t reach settings
NotFoundException404
ConflictException409
UnprocessableEntityException422
ServiceException5xx
ApiExceptioneverything 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, waitFor helpers, 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.

Still have questions?

Instant article search