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

# Integration best practices

> Guidance for building resilient, production-grade integrations with the Documents API

This page provides best practices for integrating with the Documents API, focusing on resilience, security, and graceful handling of API evolution.

***

## Handling API evolution gracefully

The Documents API evolves continuously to support new document types, detection capabilities, and customer requirements. Integrations should handle changes gracefully without breaking.

### New enum values may appear without notice

<Warning>
  **Adding new enum values is not considered a breaking change.** New values may be introduced at any time without prior notice or version bumps.
</Warning>

Enum fields that may expand:

* `score` values (e.g., new fraud risk levels beyond `NORMAL`, `TRUSTED`, `WARNING`, `HIGH_RISK`)
* `status` values (e.g., new terminal or intermediate statuses)
* `indicator.type` values (new indicator types beyond `RISK`, `TRUST`, `INFO`)
* `metadata.type` values (new visualization formats beyond `DataOnly`, `ElementsCollection`, `Combining`)
* Document `tags` (new classification tags)

### New API fields may appear without notice

<Warning>
  **Adding new response fields is not considered a breaking change.** New fields may be introduced at any time.
</Warning>

Your integration should:

* **Ignore unknown fields** — do not fail on unexpected JSON keys
* **Use permissive JSON parsing** — most modern libraries ignore extra fields by default
* **Avoid strict schema validation in production** — use it in tests, but allow unknown fields in prod

**Example (Python):**

```python theme={null}
# Bad: strict dataclass fails on new fields
@dataclass
class FraudResult:
    status: str
    score: str
    indicators: list
    # No __post_init__ to ignore extras → breaks on new field

# Good: ignores unknown fields
fraud_result = response.json()
status = fraud_result.get("status")
score = fraud_result.get("score")
indicators = fraud_result.get("indicators", [])
# Unknown fields are silently ignored
```

### Always use `metadata.type` to determine visualization format

<Warning>
  **Do not hardcode which indicators have which metadata types.** The metadata format for a given indicator can change over time as detection capabilities improve.
</Warning>

Indicator metadata types can evolve:

* An indicator that was `DataOnly` (no visualization) may become `Combining` (structured tables) in a future release
* An `ElementsCollection` indicator may gain additional element types
* New metadata types may be introduced

**Recommended approach:**

Always read `metadata.type` from the response and switch on that value:

```python theme={null}
for indicator in indicators:
    metadata = indicator.get("metadata", {})
    if not metadata:
        render_text_only(indicator.get("title", "Unknown indicator"))
        continue
    
    metadata_type = metadata.get("type")
    
    if metadata_type == "Combining":
        render_combining_table(metadata)
    elif metadata_type == "ElementsCollection":
        render_elements_collection(metadata)
    elif metadata_type == "DataOnly":
        render_data_table(metadata)
    else:
        # Unknown type → fallback to text-only
        render_text_only(indicator.get("title", "Unknown indicator"))
```

**Do not do this:**

```python theme={null}
# Bad: hardcoded mapping breaks when metadata.type changes
INDICATOR_METADATA_MAP = {
    "in_transaction_cluster": "Combining",
    "in_font_inconsistency": "ElementsCollection",
    # ... hardcoded for all indicators
}

# This breaks when we change an indicator's metadata.type
```

### Use `metadata.title` for table headers

The `metadata.title` field (introduced in recent releases) provides a human-readable title for indicator metadata tables.

**Rules:**

* If `metadata.title` is present, display it as the table heading
* If `metadata.title` is absent, do not display a table heading
* `metadata.title` is optional and may be `null` or missing

**Example:**

```python theme={null}
metadata = indicator.get("metadata", {})
title = metadata.get("title")

if title:
    print(f"### {title}")

render_table_rows(metadata.get("rows", []))
```

### Format back-ticked text as monospace code

When rendering metadata elements that contain text fields (e.g., `BBoxWithOldAndNewText` elements with `original_text` and `new_text`), text wrapped in backticks (`text`) should be formatted as monospace code.

**Example:**

<Frame>
  <img src="https://mintcdn.com/resistantai/XHK_RKxS2P-nBrw5/images/backticked_text_formating.png?fit=max&auto=format&n=XHK_RKxS2P-nBrw5&q=85&s=498744c04af5ed4496dfe2a22cfd31c5" alt="Backticked Text Formating" title="Backticked Text Formating" className="mx-auto" style={{ width:"39%" }} width="644" height="852" data-path="images/backticked_text_formating.png" />
</Frame>

This improves readability for technical fields (e.g., font names, metadata fields, encoded values).

### Do not hardcode indicator IDs or indicator names

<Warning>
  **Indicator IDs and names evolve over time.** Resistant AI continuously adds, deprecates, and improves indicators as detection capabilities advance.
</Warning>

Your integration should:

* **Never hardcode indicator IDs** (e.g., `in_transaction_cluster`, `in_font_inconsistency`) in business logic
* **Never build conditional logic around specific indicator names** (e.g., "if indicator ID contains X, then do Y")
* **Use indicator properties dynamically** — rely on `type`, `score`, `severity`, or `metadata.type` instead

**Why this matters:**

* Indicators may be split, merged, or renamed as detection models improve
* New indicators are added regularly
* Deprecated indicators may be removed or replaced

If you need indicator-specific handling, contact Resistant AI support to discuss stable integration points.

### Consider using Web UI or iFrame viewer for visualization

If building a custom UI for fraud indicators is not a core requirement, consider:

1. **Web UI** — let users view results in the hosted Web UI (no custom UI needed)
2. **iFrame viewer** — embed Resistant AI's viewer in your application

**Benefits:**

* No custom parsing or rendering logic
* Automatic support for new metadata types
* Consistent user experience
* Reduced maintenance burden

See:

* [Using the Web UI](/view-results/web-ui/using-web-ui)
* [iFrame viewer](/view-results/iframe/quickstart)

<Tip>
  If you only need to display fraud results occasionally (e.g., for manual review), linking to the Web UI or embedding the iFrame is often simpler than building a custom renderer.
</Tip>

## Fetching fraud results

### Use `with_metadata` parameter appropriately

The `GET /v2/submission/{submission_id}/fraud` endpoint accepts an optional `with_metadata` query parameter (defaults to `false`).

**When to use `with_metadata=true`:**

* You are displaying indicator details in a custom UI
* You need structured metadata for rendering tables, charts, or visualizations
* You are building a manual review interface with detailed fraud evidence

**When to omit it (default `false`):**

* You only need the overall `score` or `decision` for routing
* You are auto-approving/declining based on score alone
* You want faster response times and smaller payloads

**Performance impact:**

Responses with `with_metadata=true` are larger and may take slightly longer to return. If you only need the fraud score or decision, omit the parameter to optimize performance.

**Example:**

```http theme={null}
# Minimal response (score + decision only)
GET /v2/submission/{submission_id}/fraud

# Full response with indicator metadata
GET /v2/submission/{submission_id}/fraud?with_metadata=true
```

### Handling missing or null fields

Not all fields in the fraud result response are guaranteed to be present. Your integration should handle missing or null values gracefully.

**Fields that may be missing or `null`:**

| Field            | When missing or `null`                                                                      |
| ---------------- | ------------------------------------------------------------------------------------------- |
| `decision`       | Adaptive Decision not enabled for tenant, or `enable_decision: false` in submission request |
| `query_id`       | Not provided in the original submission request                                             |
| `indicators`     | May be an empty array for certain document types, or if analysis fails                      |
| `metadata`       | Only present if `with_metadata=true` in the request                                         |
| `metadata.title` | Optional field; may be `null` or missing even when metadata is present                      |

***
