> ## 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.

***

### 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,
)
```

**Parameters**

<ParamField body="text" type="string" required>
  The text to be classified.
</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 input text.
</ResponseField>

<ResponseField name="version" type="string">
  The API version identifier (e.g., `"3.0"`).
</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 (`"AI"`, `"AI-Assisted"`, `"Human"`, `"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 (e.g., `"AI-Generated"`, `"Moderately AI-Assisted"`).
    </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 original text.
    </ResponseField>

    <ResponseField name="end_index" type="integer">
      Ending character index in the original 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>
  </Expandable>
</ResponseField>

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)
```

This is equivalent to:

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

The `timeout` and `poll_interval` 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"},
])

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>

**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.

***

### 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.

***

### 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.

***

## 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)
```

### 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"])
```

## 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.
