> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pangram.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> Reference for the Pangram Python client library

## Installation

```bash theme={null}
pip install pangram-sdk
```

## Pangram

The main client class for interacting with the Pangram Labs API.

`PangramText` remains available for legacy imports, but `Pangram` is the recommended client name.

### Constructor

```python theme={null}
from pangram import Pangram

client = Pangram(api_key="your-api-key")
```

<ParamField body="api_key" type="string">
  Your API key for Pangram Labs. If not provided, the `PANGRAM_API_KEY` environment variable will be used.
</ParamField>

Raises `ValueError` if the API key is not provided and not set in the environment.

***

### list\_models()

List the detection models available to this API key.

```python theme={null}
available_models = client.list_models()
# e.g., ["default", "pangram-4"]
```

The returned `list[str]` preserves server order and reflects both the account's entitlements and current model availability. Do not hard-code the catalog or assume every API key sees the same values.

Use one returned selector as the keyword-only `model` argument on text and bulk requests.

<Warning>
  The `model` argument is temporarily optional for backward compatibility. Omitting it preserves the existing wire payload, selects Pangram's server default, and emits a `DeprecationWarning`. Pass `model="default"` or another value from `list_models()` in new code. The SDK will require explicit model selection after September 30, 2026.
</Warning>

***

### predict()

Classify text as AI-generated, AI-assisted, or human-written. `predict()` submits an async inference task, polls until the task completes, and returns the completed task payload.

```python theme={null}
result = client.predict(
    text,
    public_dashboard_link=False,
    timeout=300,
    poll_interval=0.5,
    model="pangram-4",
)
```

**Parameters**

<ParamField body="text" type="string" required>
  The text to be classified.
</ParamField>

<ParamField body="model" type="string">
  Keyword-only model selector returned by `list_models()`. Pass `"default"` to follow Pangram's current default model.
</ParamField>

<ParamField body="public_dashboard_link" type="boolean" default="false">
  Whether to include a public dashboard link in the completed response.
</ParamField>

<ParamField body="timeout" type="float" default="300">
  Maximum number of seconds to wait for the async task to complete.
</ParamField>

<ParamField body="poll_interval" type="float" default="0.5">
  Number of seconds to wait between polling attempts. Values below `0.1` are clamped to `0.1`.
</ParamField>

**Returns**

A dictionary with the following fields after the task reaches `STAGE_SUCCESS`:

<ResponseField name="stage" type="string">
  Terminal async task stage. Successful responses return `"STAGE_SUCCESS"`.
</ResponseField>

<ResponseField name="text" type="string">
  The analyzed text returned by the model. Pangram 4 may normalize the submitted text before inference; window offsets refer to this value.
</ResponseField>

<ResponseField name="version" type="string">
  The API version identifier (e.g., `"4.0"` for Pangram 4).
</ResponseField>

<ResponseField name="headline" type="string">
  Classification headline summarizing the result.
</ResponseField>

<ResponseField name="prediction" type="string">
  Long-form prediction string describing the classification.
</ResponseField>

<ResponseField name="prediction_short" type="string">
  Short-form prediction string. Pangram 4 returns `"AI"`, `"Human"`, or `"Mixed"`.
</ResponseField>

<ResponseField name="fraction_ai" type="float">
  Fraction of text classified as AI-written (0.0–1.0).
</ResponseField>

<ResponseField name="fraction_ai_assisted" type="float">
  Fraction of text classified as AI-assisted (0.0–1.0).
</ResponseField>

<ResponseField name="fraction_human" type="float">
  Fraction of text classified as human-written (0.0–1.0).
</ResponseField>

<ResponseField name="num_ai_segments" type="integer">
  Number of text segments classified as AI.
</ResponseField>

<ResponseField name="num_ai_assisted_segments" type="integer">
  Number of text segments classified as AI-assisted.
</ResponseField>

<ResponseField name="num_human_segments" type="integer">
  Number of text segments classified as human.
</ResponseField>

<ResponseField name="dashboard_link" type="string">
  Dashboard link. Only present when `public_dashboard_link` is `True`.
</ResponseField>

<ResponseField name="windows" type="list">
  List of text windows and their classifications. Each window contains:

  <Expandable title="Window object properties">
    <ResponseField name="text" type="string">
      The window text.
    </ResponseField>

    <ResponseField name="label" type="string">
      Classification label. Pangram 4 returns `"AI-Generated"`, `"AI-Assisted"`, or `"Human Written"`.
    </ResponseField>

    <ResponseField name="ai_assistance_score" type="float">
      AI assistance score (0.0–1.0), where 0 means no AI assistance and 1.0 means AI-generated.
    </ResponseField>

    <ResponseField name="confidence" type="string">
      Confidence level (`"High"`, `"Medium"`, `"Low"`).
    </ResponseField>

    <ResponseField name="start_index" type="integer">
      Starting character index in the returned top-level `text`.
    </ResponseField>

    <ResponseField name="end_index" type="integer">
      End-exclusive character index in the returned top-level `text`.
    </ResponseField>

    <ResponseField name="word_count" type="integer">
      Number of words in the window.
    </ResponseField>

    <ResponseField name="token_length" type="integer">
      Token length of the window.
    </ResponseField>

    <ResponseField name="is_humanized" type="boolean">
      Whether Pangram 4's humanizer head classified the window as humanized. Present on every Pangram 4 window.
    </ResponseField>

    <ResponseField name="humanizer_score" type="float">
      Pangram 4 humanizer-head score from 0.0–1.0. Present on every Pangram 4 window.
    </ResponseField>
  </Expandable>
</ResponseField>

Results remain normal dictionaries. Typed applications can import `PredictionResult` and `PredictionWindow` from `pangram`.

Raises `ValueError` if the API returns an error, the task fails, or the response is invalid. Raises `TimeoutError` if the task does not complete before `timeout`.

***

### predict\_with\_dashboard\_link()

Classify text and include a public dashboard link in the completed response.

```python theme={null}
result = client.predict_with_dashboard_link(
    text,
    timeout=300,
    poll_interval=0.5,
    model="pangram-4",
)
```

This is equivalent to:

```python theme={null}
result = client.predict(
    text,
    public_dashboard_link=True,
    timeout=300,
    poll_interval=0.5,
    model="pangram-4",
)
```

The `timeout`, `poll_interval`, and keyword-only `model` parameters have the same behavior as `predict()`.

***

### submit\_bulk()

Submit a Bulk API job for asynchronous AI detection across many inputs.

Provide exactly one of `text` or `items`.
Bulk jobs are processed asynchronously. Completion time depends on the number and length of submitted items and current system load. Use `get_bulk_status()` or `wait_for_bulk()` to monitor progress.

Use `text=[...]` for plain string inputs, or `items=[{"id": "...", "text": "..."}]` when you want customer IDs returned with status and results. Do not pass both.

```python theme={null}
bulk = client.submit_bulk(
    items=[
        {"id": "row-001", "text": "First text to analyze"},
        {"id": "row-002", "text": "Second text to analyze"},
    ],
    model="pangram-4",
)

bulk_id = bulk["bulk_id"]
```

**Parameters**

<ParamField body="text" type="list[string]">
  List of input texts. Use this shape when you do not need customer item IDs.
</ParamField>

<ParamField body="items" type="list[dict]">
  List of item dictionaries. Each item must include `text` and may include a unique customer-defined `id`.
</ParamField>

<ParamField body="model" type="string">
  Keyword-only model selector returned by `list_models()`. Applies to the entire bulk job; per-item model selectors are not supported.
</ParamField>

**Returns**

<ResponseField name="bulk_id" type="string">
  The ID of the bulk job.
</ResponseField>

<ResponseField name="status" type="string">
  Initial status. Usually `queued`; returns `failed` if every item failed immediate validation.
</ResponseField>

<ResponseField name="total_items" type="integer">
  Total number of submitted items.
</ResponseField>

<ResponseField name="accepted_items" type="list">
  Items accepted for processing. Each item includes `index`, optional `id`, and `task_id`.
</ResponseField>

<ResponseField name="failed_items" type="list">
  Items that failed immediate validation. Each item includes `index`, optional `id`, `task_id: None`, `stage`, and `error`.
</ResponseField>

***

### wait\_for\_bulk()

Poll a bulk job until it reaches a terminal status.

```python theme={null}
status = client.wait_for_bulk(
    bulk_id,
    timeout=3600,
    poll_interval=0.5,
)
```

Terminal statuses are `succeeded`, `failed`, and `partial`.
Completion time depends on the number and length of submitted items and current system load.

**Parameters**

<ParamField body="bulk_id" type="string" required>
  The bulk job ID returned by `submit_bulk()`.
</ParamField>

<ParamField body="timeout" type="float" default="3600">
  Maximum number of seconds to wait for terminal completion.
</ParamField>

<ParamField body="poll_interval" type="float" default="0.5">
  Number of seconds to wait between polling attempts. Values below `0.1` are clamped to `0.1`.
</ParamField>

**Returns**

The same dictionary returned by `get_bulk_status()` after the job reaches a terminal status.

Raises `TimeoutError` if the job does not complete before `timeout`.

***

### get\_bulk\_status()

Fetch the current status and counters for a bulk job.

```python theme={null}
status = client.get_bulk_status(bulk_id)
```

**Returns**

<ResponseField name="bulk_id" type="string">
  The ID of the bulk job.
</ResponseField>

<ResponseField name="status" type="string">
  One of `queued`, `running`, `succeeded`, `failed`, or `partial`.
</ResponseField>

<ResponseField name="total_items" type="integer">
  Total number of submitted items.
</ResponseField>

<ResponseField name="accepted" type="integer">
  Number of items accepted for processing.
</ResponseField>

<ResponseField name="succeeded" type="integer">
  Number of items that completed successfully.
</ResponseField>

<ResponseField name="failed" type="integer">
  Number of items that failed.
</ResponseField>

<ResponseField name="created_at" type="string">
  Job creation timestamp as Unix epoch seconds encoded as a string.
</ResponseField>

<ResponseField name="completed_at" type="string">
  Job completion timestamp as Unix epoch seconds encoded as a string. `None` while the job is not terminal.
</ResponseField>

***

### get\_bulk\_items()

Fetch paginated item metadata for a bulk job.

```python theme={null}
items = client.get_bulk_items(bulk_id, offset=0, limit=100)
```

**Parameters**

<ParamField body="bulk_id" type="string" required>
  The bulk job ID returned by `submit_bulk()`.
</ParamField>

<ParamField body="offset" type="integer" default="0">
  Zero-based item offset.
</ParamField>

<ParamField body="limit" type="integer" default="100">
  Maximum number of items to return. The API allows up to `1000`.
</ParamField>

**Returns**

A dictionary containing `bulk_id`, `offset`, `limit`, `total_items`, and `items`. Each item includes `index`, optional `id`, `task_id`, `stage`, and optional `error`.

***

### get\_bulk\_results()

Fetch all available results for a bulk job.

```python theme={null}
results = client.get_bulk_results(bulk_id)

for item in results["items"]:
    if item["result"] is not None:
        print(item["id"], item["result"]["prediction_short"])

for failed in results["failed_items"]:
    print(failed["id"], failed["error"])
```

**Parameters**

<ParamField body="bulk_id" type="string" required>
  The bulk job ID returned by `submit_bulk()`.
</ParamField>

<ParamField body="page_size" type="integer" default="1000">
  Number of submitted item slots to request per API call. The API allows up to `1000`.
</ParamField>

**Returns**

A dictionary containing `bulk_id`, `total_items`, `items`, and `failed_items` aggregated across every results page. Successful completed items include `result` with the same shape returned by `predict()`. In-progress items have `result` set to `None`.

`get_bulk_results()` materializes all pages into memory. For large jobs, use `get_bulk_results_page()` in a loop and process each page as it arrives.

The SDK returns this dictionary as `BulkResults`; successful item results use `PredictionResult`.

***

### get\_bulk\_results\_page()

Fetch one paginated results page for a bulk job.

```python theme={null}
page = client.get_bulk_results_page(bulk_id, offset=0, limit=100)
```

Use this when you need incremental page-level inspection before fetching or storing the full result set.

```python theme={null}
offset = 0
limit = 1000

while True:
    page = client.get_bulk_results_page(bulk_id, offset=offset, limit=limit)
    for item in page["items"]:
        process(item)
    for failed in page["failed_items"]:
        handle_failure(failed)

    offset += limit
    if offset >= page["total_items"]:
        break
```

**Parameters**

<ParamField body="bulk_id" type="string" required>
  The bulk job ID returned by `submit_bulk()`.
</ParamField>

<ParamField body="offset" type="integer" default="0">
  Zero-based submitted-item offset.
</ParamField>

<ParamField body="limit" type="integer" default="100">
  Maximum number of submitted item slots to return. The API allows up to `1000`.
</ParamField>

**Returns**

A dictionary containing `bulk_id`, `offset`, `limit`, `total_items`, `items`, and `failed_items` for the requested page.

The SDK returns this dictionary as `BulkResultsPage`.

***

### check\_plagiarism()

Check text for potential plagiarism against a database of online content.

```python theme={null}
result = client.check_plagiarism(text)
```

**Parameters**

<ParamField body="text" type="string" required>
  The text to check for plagiarism.
</ParamField>

**Returns**

A dictionary with the following fields:

<ResponseField name="text" type="string">
  The input text.
</ResponseField>

<ResponseField name="plagiarism_detected" type="boolean">
  Whether plagiarism was detected.
</ResponseField>

<ResponseField name="plagiarized_content" type="list">
  List of detected plagiarized content with source URLs.
</ResponseField>

<ResponseField name="total_sentences" type="integer">
  Total number of sentences checked.
</ResponseField>

<ResponseField name="plagiarized_sentences" type="list">
  List of sentences detected as plagiarized.
</ResponseField>

<ResponseField name="percent_plagiarized" type="float">
  Percentage of text detected as plagiarized.
</ResponseField>

Raises `ValueError` if the API returns an error.

***

## Typed response contracts

The SDK ships a `py.typed` marker and exports its public `TypedDict` contracts from `pangram`:

```python theme={null}
from pangram import (
    BulkResultItem,
    BulkResultMetadata,
    BulkResults,
    BulkResultsPage,
    PredictionResult,
    PredictionWindow,
)
```

| Type                 | Represents                                                            |
| -------------------- | --------------------------------------------------------------------- |
| `PredictionWindow`   | One classified window, including optional Pangram 4 humanizer fields. |
| `PredictionResult`   | A successful text prediction.                                         |
| `BulkResultMetadata` | Status metadata shared by successful and failed bulk items.           |
| `BulkResultItem`     | Bulk item metadata plus its prediction when processing succeeds.      |
| `BulkResultsPage`    | One paginated bulk results response.                                  |
| `BulkResults`        | All bulk results pages aggregated by the SDK.                         |

The runtime values remain ordinary dictionaries and lists; these types provide static checking without changing existing access patterns.

***

## Deprecated Methods

<Warning>
  The following SDK compatibility methods are deprecated and may be removed on August 1, 2026. Use `predict()` for one-off calls or `submit_bulk()` for asynchronous bulk jobs.
</Warning>

### predict\_short() <Badge color="red">Deprecated</Badge>

Forwards to `predict()` and returns the current async result schema.

```python theme={null}
result = client.predict_short(text, model="pangram-4")
```

### batch\_predict() <Badge color="red">Deprecated</Badge>

Calls `predict()` once per input text. Use `submit_bulk()` for asynchronous bulk jobs.

```python theme={null}
results = client.batch_predict(
    ["text one", "text two"],
    model="pangram-4",
)
```

## Removed Legacy Methods

`predict_extended()` and `predict_sliding_window()` are no longer part of the current `pangram-sdk`. Use `predict()` for current AI detection and segment-level results.
