# SDKs

The TypeScript and Python SDKs are generated from the review API's OpenAPI document, version 1.0.0. Each operation in the [API reference](/docs/api/) that returns JSON is a typed method, and both SDKs add pagination, waiting for a review, retries and typed errors.

## Install

The TypeScript package is `@proofline/sdk` and needs Node 22 or newer. The Python package needs Python 3.11 or newer. Neither package is on npm or PyPI yet; ask us for a build while they are not.

Both clients take a token explicitly or read `PROOFLINE_TOKEN` from the environment. They keep the token in memory, never write it to disk, and redact it from their diagnostic output.

## Create a client

```typescript
import { ProoflineClient } from "@proofline/sdk";

const client = new ProoflineClient({
  baseUrl: "https://proofline.sh",
  token: process.env.PROOFLINE_TOKEN!,
});
```

```python
import os
from proofline import ProoflineClient

client = ProoflineClient(os.environ["PROOFLINE_TOKEN"])
```

```sh
# curl has no client to create: every request carries the token itself.
curl -sS "https://proofline.sh/api/v1/organizations/$PROOFLINE_ORGANIZATION_ID/reviews?limit=1" \
  -H "Authorization: Bearer $PROOFLINE_TOKEN"
```

The Python client holds HTTP connections, so use it as a context manager or call `close()`. `AsyncProoflineClient` has the same methods as coroutines. The TypeScript client uses the platform's `fetch` and has nothing to close.

## Page through reviews

`iterReviews` and `iter_reviews` follow the cursor from page to page with the same filters. They stop after 100 pages or 10,000 reviews unless you set other limits, and they refuse a cursor that repeats.

```typescript
import { ProoflineClient } from "@proofline/sdk";

const client = new ProoflineClient({
  baseUrl: "https://proofline.sh",
  token: process.env.PROOFLINE_TOKEN!,
});
const reviews = client.iterReviews(
  { path: { org_id: process.env.PROOFLINE_ORGANIZATION_ID! } },
  { maxItems: 50 },
);
for await (const review of reviews) {
  console.log(review.id, review.kind, review.status);
}
```

```python
import os
from uuid import UUID
from proofline import ProoflineClient

with ProoflineClient(os.environ["PROOFLINE_TOKEN"]) as client:
    organization_id = UUID(os.environ["PROOFLINE_ORGANIZATION_ID"])
    for review in client.iter_reviews(organization_id, max_items=50):
        print(review.id, review.kind, review.status)
```

```sh
# The SDKs follow the cursor for you. Over HTTP, this reads the first page.
# While a page's "truncated" is true, request the next one with the same
# filters and that page's "next_cursor" as "cursor".
curl -sS "https://proofline.sh/api/v1/organizations/$PROOFLINE_ORGANIZATION_ID/reviews?limit=50" \
  -H "Authorization: Bearer $PROOFLINE_TOKEN"
```

## Wait for a review

`waitForReview` and `wait_for_review` poll a review until it finishes or the deadline passes. They wait 5 seconds first, then double the wait up to 60 seconds. The result has a status, the review's console address and what each assessment could not establish.

| Status | Meaning |
| --- | --- |
| `COMPLETED` | Every assessment finished and recorded its checks. |
| `INCOMPLETE` | The review finished, but an assessment has an unknown state, an unresolved check or an evidence gap. |
| `FAILED` | The review failed. |
| `STOPPED` | The review stopped before it finished. |
| `TIMED_OUT` | The deadline passed first. The result keeps the latest review it read. |

A completed review tells you the review ran. Read its findings and evidence before you decide to deploy.

```typescript
import { ProoflineClient, ProoflineError } from "@proofline/sdk";

const client = new ProoflineClient({
  baseUrl: "https://proofline.sh",
  token: process.env.PROOFLINE_TOKEN!,
});
const orgId = process.env.PROOFLINE_ORGANIZATION_ID!;

try {
  const page = await client.listReviews({ path: { org_id: orgId }, query: { limit: 1 } });
  const latest = page.items[0];
  if (latest !== undefined) {
    const result = await client.waitForReview(orgId, latest.id, { timeoutMs: 20 * 60_000 });
    console.log(result.status, result.reviewUrl);
    for (const gap of result.gaps) {
      console.log(gap.assessmentId, gap.reasonCodes);
    }
  }
} catch (error) {
  if (error instanceof ProoflineError) console.error(error.status, error.type);
  else throw error;
}
```

```python
import os
from uuid import UUID
from proofline import ProoflineClient, ProoflineError

try:
    with ProoflineClient(os.environ["PROOFLINE_TOKEN"]) as client:
        organization_id = UUID(os.environ["PROOFLINE_ORGANIZATION_ID"])
        page = client.list_reviews(organization_id, limit=1)
        for review in page.items:
            result = client.wait_for_review(organization_id, review.id, timeout=20 * 60)
            print(result.status, result.review_url)
            for gap in result.gaps:
                print(gap.assessment_id, gap.reason_codes)
except ProoflineError as error:
    print(error.status, error.type)
```

```sh
# The SDKs poll for you and derive the statuses above from the review's
# assessments. Over HTTP, read the review again until its status is no longer
# PENDING or RUNNING, then read each assessment's gaps yourself.
curl -sS "https://proofline.sh/api/v1/organizations/$PROOFLINE_ORGANIZATION_ID/reviews/$REVIEW_ID" \
  -H "Authorization: Bearer $PROOFLINE_TOKEN"
```

## Errors and retries

A refused request raises `ProoflineError` with the problem's `status`, `type` and `detail`. An invalid response or a network failure raises it with a fixed SDK problem type.

The clients retry a GET that returns 429 or 503 up to 3 times. They wait for `Retry-After` up to 60 seconds and refuse a longer one. A review request or rerun is sent once, because repeating a pull request review request or a rerun can start another review. Requests time out after 30 seconds, and a response body is limited to 8 MiB.
