Metadata-Version: 2.5
Name: archeai-sdk
Version: 1.1.0
Summary: Official Python SDK for the ArcheAI behavioral intelligence API — executive behavioral histories, deception-relevant signals, personality, and communication-style analytics from speech.
Project-URL: Homepage, https://arche-ai.com
Project-URL: Documentation, https://pypi.org/project/archeai-sdk/
Project-URL: API Reference, https://api.arche-ai.com/docs/
Project-URL: Source, https://arche-ai.com
Author-email: ArcheAI <contact@arche-ai.com>
License: MIT
Keywords: alternative-data,behavioral-analytics,earnings-calls,nlp,quantitative-finance,voice-analysis
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.9
Requires-Dist: requests>=2.28
Provides-Extra: pandas
Requires-Dist: pandas>=1.5; extra == 'pandas'
Provides-Extra: quant
Requires-Dist: lightgbm>=4.0; extra == 'quant'
Requires-Dist: pandas>=1.5; extra == 'quant'
Requires-Dist: pyarrow>=14; extra == 'quant'
Requires-Dist: scikit-learn>=1.3; extra == 'quant'
Description-Content-Type: text/markdown

# archeai-sdk

Official Python SDK for the [ArcheAI](https://arche-ai.com) behavioral
intelligence API — executive behavioral histories, multi-speaker session
analysis, fraud-language, personality, and communication-style
analytics from speech.

```bash
pip install "archeai-sdk[pandas]"   # DataFrame outputs
pip install "archeai-sdk[quant]"    # + the event-study kit's backends (pyarrow, lightgbm, scikit-learn)
```

## Documentation

The complete reference is inlined below (PyPI-hosted copy of the SDK docs; same content as the package's `docs/` folder and every method's docstring).

| | |
|---|---|
| [Getting started](#getting-started) | install, auth, the three request shapes, which method to use, credits, trials, data handling |
| [Quant: behavioral history](#quant) | `behavioral_history()` — every parameter and every output column |
| [Event-study kit](#event-study) | `event_study` — the published horserace recipe (point-in-time self-baseline features, within-quarter rank IC, baseline vs baseline+ArcheAI) runnable on your own returns |
| [Quant demo notebook](examples/quant_demo.ipynb) ([rendered](examples/quant_demo.html)) | end to end on a 50-ticker pilot key: bulk export (or per-ticker fallback) → point-in-time features → your returns (CSV template; the demo ships synthetic returns and says so) → horserace with the baseline named → how to read it. Runs cold in ~30 s |
| [Text analyses](#text) | fraud-language, personality, style, batch — parameters + result fields |
| [Audio (single speaker)](#audio) | upload, deception, voice personality, pair, convergence, one-call flow |
| [Sessions (multi-speaker)](#sessions) | `analyze_session()` — diarization + per-speaker scores + time series, PHI mode |
| [Jobs, account, models](#jobs) | polling, credits, model catalog |
| [Interpreting scores](#interpreting-scores) | what z-scores, 1–7 traits, and 0–1 signals mean — and their limits |
| [Errors](#errors) | every error code and what to do |

Every public method also carries the same documentation in its docstring
(`help(ArcheClient.analyze_session)`), and the REST API's own reference is
at [api.arche-ai.com/docs](https://api.arche-ai.com/docs).

## 30-second tour

```python
from arche_ai import ArcheClient
c = ArcheClient(api_key="arche_sk_...")

# Quant: one row per (executive, quarter), point-in-time safe
df = c.behavioral_history("AAPL", from_q="2018Q1")

# Text: async job -> wait -> result dict
style = c.wait(c.analyze_style(exec_answers)).result

# Single-speaker clip: upload -> analyze -> wait -> (delete), one call
out = c.analyze_file("clip.wav", lines=("voice-personality",),
                     delete_after=True)

# Multi-speaker recording: diarize + transcribe + per-speaker scores
sess = c.analyze_session("interview.wav", phi=True)   # HIPAA-mode example
sess.speaker_frame()      # per-speaker Big Five + style
sess.segment_frame()      # per-speaker behavioral time series
```

## Quant quickstart

```python
from arche_ai import ArcheClient

c = ArcheClient(api_key="arche_sk_...")

# Flagship: per-executive quarterly behavioral trajectories, from
# ArcheAI's 1.8M-earnings-call research corpus (2002-present).
# Point-in-time safe: each quarter's scores derive only from that
# quarter's calls; history is append-only — safe for backtests.
df = c.behavioral_history("AAPL", from_q="2018Q1")
#   one row per (executive, quarter):
#   7 communication-style z-scores + Big Five estimates

# Example: flag executives breaching their own uncertainty baseline
pivot = df.pivot_table(index="quarter", columns="executive",
                       values="style_uncertain_certain")
z = (pivot - pivot.expanding().mean()) / pivot.expanding().std()
print(z.tail(4))  # who no longer sounds like themselves?
```

These are the same measures behind ArcheAI's published event-study
results (out-of-sample lift over price, Loughran-McDonald sentiment,
and SUE baselines at 1–5 day horizons).

## Text analyses (async jobs)

```python
job = c.analyze_fraud_language(qa_transcript_text)  # >= 200 chars
job = c.analyze_personality(text)
job = c.analyze_style(text)

result = c.wait(job)          # poll to completion
print(result.result)
```

## Audio (bring your own recordings)

```python
audio_id = c.upload_audio("interview.wav")   # one call: 3-step flow
job = c.analyze_voice_personality(audio_id)  # voice-only Big Five (v2 artifact)
final = c.wait(job)
```

Uploaded audio is automatically deleted from storage after 7 days;
delete sooner via the API or your account's My Data page.

## Utilities

```python
c.models()    # available models + per-call credit costs
c.account()   # remaining credits
```

## Important framing

All scores are **research signals intended to inform professional
judgment** — not investment advice, accusations, lie detection, or
determinations of fact. Validation methodology, published nulls, and
boundary conditions are documented at
[api.arche-ai.com/docs](https://api.arche-ai.com/docs).

## Roadmap

- Speaker identification against ArcheAI's executive voiceprint
  database (`speakers="auto"`)
- Bulk/point-in-time history export for backtesting
- Event-study starter kit (reproduce our published results on your
  own universe)

© ArcheAI. Contact: contact@arche-ai.com


---

# Reference

<a id="getting-started"></a>

## Getting started

```bash
pip install archeai-sdk            # core (requests only)
pip install "archeai-sdk[pandas]"  # DataFrame outputs
pip install "archeai-sdk[quant]"   # + pyarrow, lightgbm, scikit-learn: the event-study kit and the demo notebook
```

```python
from arche_ai import ArcheClient
c = ArcheClient(api_key="arche_sk_...")        # from the web app → Settings → API Keys
c.account()                                     # {"credits": ..., "trial": {...}?}
```

`ArcheClient(api_key, base_url="https://api.arche-ai.com", dataframes=True, timeout=60.0)`

| arg | meaning |
|---|---|
| `api_key` | Workspace-scoped key. Credits are deducted from the key's **workspace**. |
| `base_url` | Override for staging. |
| `dataframes` | Return pandas DataFrames where natural; `False` → lists of dicts. Degrades gracefully if pandas isn't installed. |
| `timeout` | Per-request HTTP timeout in seconds. |

### The three shapes of request

1. **Synchronous reads** — `behavioral_history()`, `account()`, `models()`, `audio_status()`. Return immediately.
2. **Async analysis jobs** — every `analyze_*` method returns a `Job` (`status="queued"`). Credits are deducted at submission. Poll with `wait(job)`; the finished `Job.result` is a dict whose fields are documented per method.
3. **One-call conveniences** — `analyze_file()` (single-speaker clip) and `analyze_session()` (multi-speaker recording) do upload → analyze → wait → assemble for you.

```python
job = c.analyze_style(text)        # queued, credits deducted
done = c.wait(job)                  # polls until completed/failed
done.result["dimensions"]["uncertain_certain"]["score"]
```

### Which method do I want?

| I have… | Use |
|---|---|
| A ticker and want executive behavior over time | `behavioral_history("AAPL")` |
| Text from one person | `analyze_fraud_language` / `analyze_personality` / `analyze_style` |
| Many texts | `analyze_texts(line, texts)` |
| A clip of one speaker | `analyze_file(path)` → deception + voice personality |
| Two clips of the **same** speaker | `upload_audio` ×2 → `analyze_deception_pair` |
| Two clips of **different** speakers | `upload_audio` ×2 → `analyze_convergence` |
| A recording with several speakers (interview, session, meeting) | `analyze_session(path)` |
| Protected health information | `analyze_session(path, phi=True)` inside a BAA-covered workspace |

### Credits (per call, as of SDK 0.3)

| call | credits |
|---|---|
| text analyses | 1 each |
| behavioral_history | 5 |
| single-clip audio lines | 10 each |
| session | 40 (includes vendor transcription) |

`models()` is authoritative — costs live in the model registry, not the SDK.

### Trials

Trial workspaces may be restricted to a ticker allow-list, a rolling history
window, a daily request cap, and an expiry. `account()["trial"]` shows the
terms; `behavioral_history()` sets `client.last_trial`; boundary errors
raise `ArcheAPIError` with `.is_trial_limit == True`
(`TRIAL_TICKER_DENIED`, `TRIAL_DAILY_CAP`, `TRIAL_EXPIRED`). See
[errors.md](errors.md).

### Data handling

- Uploaded audio auto-deletes after **7 days**; `delete_audio()` / `delete_after=True` deletes immediately.
- Text you submit is processed and not retained beyond the job record.
- PHI: use `analyze_session(..., phi=True)` — transcript PII/PHI redaction + vendor-side purge. See [sessions.md](sessions.md).
- No LLM is used in any analysis line.

<a id="quant"></a>

## Quant: behavioral history

Point-in-time executive behavioral trajectories from the research corpus.

_All scores are research signals intended to inform professional judgment — not diagnoses, accusations, lie detection, or determinations of fact._

#### `behavioral_history()`

Per-executive quarterly behavioral trajectories for a public company.

The flagship quant endpoint (`GET /v1/companies/{ticker}/behavioral-history`):
for every executive who has spoken on the company's earnings calls, one row
per quarter with 7 communication-style dimensions and Big Five estimates,
computed only from that quarter's calls. History is append-only, so it is
safe for point-in-time backtests.

**Args**

```text
    ticker: Exchange ticker, e.g. `"AAPL"`. Case-insensitive. Must exist
        in the research corpus (~19.7K companies).
    from_q: Earliest quarter, `"YYYYQn"` (e.g. `"2018Q1"`). `None` =
        start of history (2002). On a trial workspace the trial's history
        floor overrides an earlier value.
    to_q: Latest quarter, `"YYYYQn"`. `None` = latest available.
```

**Returns**

```text
    `pandas.DataFrame` (list of dicts without pandas), one row per
    (executive, quarter):

    - `ticker`, `speaker_id` (int), `executive` (name or None)
    - `quarter` (`"2024Q1"`), `n_calls`, `n_words` — how much speech
      the quarter's scores rest on (more words = more stable)
    - `style_<dim>` — z-scores vs executive-register norms for
      intellectual_colloquial, concrete_abstract, tentative_assertive, authority_people, formal_conversational, uncertain_certain, pessimistic_optimistic. Positive = toward the second pole.
    - `big5_<trait>` — `openn`, `consc`, `extra`, `agree`,
      `neuro` on a 1-7 scale.

    After the call `client.last_trial` holds `{label, historyFrom,
    expiresAt}` for trial workspaces, else `None`.

Costs 5 credits per call.
```

**Raises**

```text
    ArcheAPIError: `NOT_FOUND` (ticker not in corpus),
        `INSUFFICIENT_CREDITS`, `FORBIDDEN`, or a trial code
        (`TRIAL_TICKER_DENIED` / `TRIAL_DAILY_CAP` / `TRIAL_EXPIRED`;
        see `ArcheAPIError.is_trial_limit`).
```

#### `export_history()`

Bulk point-in-time export of behavioral histories as one Parquet file.

**Price first, cache after.** `dry_run=True` returns the exact row count
and credit charge with every gate applied — nothing is queued, charged, or
counted against a cap. `cache="path.parquet"` saves the downloaded file
there and, on a later call with the same path, **loads it instead of
exporting again** (so re-running a notebook costs nothing); the cached
file's own `archeai.*` metadata is surfaced in `client.last_export`
with `{"fromCache": True}`.

Calls `GET /v1/companies/behavioral-history/export`, which queues a job;
the gateway counts the rows first and charges exactly
`max(5, ceil(rows / 10))` credits. The file holds the same rows and
columns `behavioral_history()` returns — for the companies in your
workspace's trial scope — plus `as_of_quarter` (== `quarter`; the
point-in-time key), `workspace_id` (watermark), `company_id`,
`company_name` and `resolved_by`. It feeds
`event_study.point_in_time_features()` unchanged.

**Access.** Bulk export is a workspace-granted model (not available by
default) and requires an active trial scope: the scope's ticker
allow-list and history floor define the file; its row cap (default
50,000) and per-day cap (default 2) bound it. Each export also counts
once against the daily behavioral-history cap.

**License.** Internal research use only, by the licensed workspace. No
redistribution, resale, or sharing of the file or its rows; no training,
fine-tuning, or distillation of models that reproduce ArcheAI outputs.
Rows are watermarked with your workspace id; the file name carries your
key prefix; the Parquet metadata (`archeai.*`) carries these terms.

**Args**

```text
    tickers: Optional list to restrict the export (upper-cased);
        intersected with the allow-list — an empty intersection raises
        `TRIAL_TICKER_DENIED`.
    from_q: Earliest quarter `"YYYYQn"`; the trial floor wins if later.
    to_q: Latest quarter `"YYYYQn"`; `None` = latest available.
    wait: `True` (default) polls the job and returns the data;
        `False` returns the queued `Job` immediately.
    timeout: Max seconds to wait for the job (a 50-ticker slice takes seconds).
    interval: Seconds between polls.
    dry_run: `True` → return the estimate dict (`rows`, `creditsRequired`,
        `creditsAvailable`, `scope`, `license`) and stop.
    cache: Local `.parquet` path. If it exists it is loaded (no API call,
        no charge); otherwise the export runs and the file is written there.
```

**Returns**

```text
    With `dry_run=True`: the estimate `dict`. With `wait=True`:
    `pandas.DataFrame` of the file (requires
    pandas + pyarrow); the export's metadata is in
    `client.last_export` (`rows`, `fileSha256`, `downloadUrl`,
    `creditsCharged`, `license`, `watermark`, `artifact_version`,
    `artifact_sha256` …). With `wait=False`: the queued `Job`.
```

**Raises**

```text
    ArcheAPIError: `FORBIDDEN` (workspace not granted),
        `EXPORT_REQUIRES_SCOPE`, `EXPORT_ROW_CAP` (`meta['rows']` /
        `meta['maxExportRows']`), `EXPORT_DAILY_CAP`,
        `INSUFFICIENT_CREDITS` (`meta['required']`), or a trial code.
    RuntimeError: the job failed server-side (credits are refunded
        automatically; the message carries the reason).
```

<a id="event-study"></a>

## Event-study kit

`arche_ai.event_study` is the published horserace recipe as runnable code, so you can
reproduce the construction — not the numbers — against **your own** returns and your own
baseline. The SDK ships no prices, no earnings surprises and no returns.

_All scores are research signals intended to inform professional judgment — not investment advice, accusations, or determinations of fact._

### What the published study did

- **Universe / panel:** ~15K earnings calls per year sampled 2017–2025 from the research corpus; one executive per call (the one who spoke most in Q&A).
- **Features (51):** the 12 quarterly level scores (7 style + 5 Big Five) and their `dev_*` self-baseline deviations — each score z-scored against the same executive's **previous 8 quarters** (≥ 4 present) — plus Q&A-dynamics and register-shift features that are *not* in the API.
- **Baseline (named every time):** price momentum (21 d / 63 d), 21-d volatility, dollar volume, and earnings surprise (SUE, EPS y/y). The published lift is **vs this baseline**, not vs zero.
- **Model:** gradient boosting on a quarter-demeaned market-adjusted return (CAR vs SPY, T+1 and T+5).
- **Evaluation:** within-quarter Spearman rank IC, averaged over test quarters with ≥ 30 events. Train ≤ 2021, test 2022–2025 (16 quarters); combinatorial purged CV (36 paths) and a fresh-sample replication as robustness.

Headline numbers, the per-year table and the 2026 forward record (v5 frozen and v5.1 annual refit, each against its own price+SUE baseline) live in `quant_headline_metrics.json` in the data room and in the quant whitepaper; this page does not restate them.

### Running the recipe on your data

```python
from arche_ai import ArcheClient
from arche_ai import event_study as es

client = ArcheClient(api_key="...")
hist = pd.concat([client.behavioral_history(t) for t in ["AAPL", "MSFT", "COST"]])

## 1. point-in-time feature block: 12 levels + dev_* from prior quarters only
feat = es.point_in_time_features(hist)

## 2. your events: ticker, call_date, your market-adjusted return, YOUR baseline columns
events = pd.read_parquet("my_events.parquet")   # ticker, call_date, car_1, mom_21, mom_63, vol_21, dollar_vol, sue, eps_yoy
panel = es.join_events(events, feat)

## 3. the horserace: baseline vs baseline + ArcheAI, within-quarter rank IC
res = es.horserace(panel, target="car_1",
                   baseline_cols=["mom_21", "mom_63", "vol_21", "dollar_vol", "sue", "eps_yoy"],
                   train_quarters=[q for q in panel.quarter.unique() if q < "2022Q1"],
                   test_quarters=[q for q in panel.quarter.unique() if q >= "2022Q1"])
print(res["baseline"], res["baseline_plus_archeai"], res["lift"], res["baseline_definition"])
```

`res["baseline_definition"]` always names the baseline you supplied. If you pass none, the
result is ArcheAI-features-only and the kit says so — it is not the published lift.

### What differs from the study, by construction

- The API exposes the 12 level scores; the study's Q&A-dynamics and register-shift features are research-only. Expect the API block to carry **part** of the published lift.
- `join_events` uses the executive with the most words in the call's quarter; the study used the most-words executive **in Q&A**. On most calls these coincide.
- Trial workspaces have a history floor (`client.last_trial`), which truncates `dev_*` for early quarters.

<a id="text"></a>

## Text analyses

Single-speaker text → fraud-language, personality, communication style. Async jobs.

_All scores are research signals intended to inform professional judgment — not diagnoses, accusations, lie detection, or determinations of fact._

#### `analyze_fraud_language()`

Fraud-language screening score for a text (`POST /v1/analyses/fraud-language`).

Mixture-of-experts score trained on SEC-enforcement-era executive speech.
For a single text the deviation expert (which needs the speaker's own
history) is excluded, so the score is the lexical + embedding mean:
company-held-out AUC 0.74 for this path; 0.87 for the full three-expert
model used at corpus scale. A research screening signal — never an
accusation or determination of fact.

**Args**

```text
    text: Unscripted speech or writing by ONE person (an executive's Q&A
        answers, an interview transcript, an essay). Minimum ~50 words;
        the models are calibrated on hundreds+. Scripted or multi-speaker
        text degrades validity.
```

**Returns**

```text
    `Job` with `status="queued"`. Pass it to `wait()`; the
    completed job's `.result` dict has the fields below.

    - `score` (0-1): higher = more similar to enforcement-era speech
    - `experts`: `{lexical, embedding, deviation}` component
      probabilities; `deviation` is `None` for one-shot texts (the
      within-speaker baseline expert needs speaker history)
    - `n_words`, `notes`, `model_version`, `disclaimer`
    - `holdout_auc`: company-held-out AUC of the single-text path that
      produced this score (0.741); `holdout_auc_full_model`: the same
      holdout for the full three-expert model (0.874). Context for the
      score, not a per-text confidence
```

**Raises**

```text
    ArcheAPIError: `BAD_REQUEST` if the text is too short.
```

#### `analyze_personality()`

Big Five personality estimate from language (`POST /v1/analyses/personality`).

Distilled from a peer-reviewed reference instrument. The API text line is
the lexicon + structural student: held-out r .64-.70 vs the reference
across all speakers, .82-.89 for speakers with 10K+ words. The served
artifact (sha256 in the response) is the number you get; the research-
corpus profiles use a separate three-expert model. Measures *expressed*
personality; not a clinical instrument.

**Args**

```text
    text: Unscripted speech or writing by ONE person (an executive's Q&A
        answers, an interview transcript, an essay). Minimum ~50 words;
        the models are calibrated on hundreds+. Scripted or multi-speaker
        text degrades validity.
```

**Returns**

```text
    `Job` with `status="queued"`. Pass it to `wait()`; the
    completed job's `.result` dict has the fields below.

    - `traits`: `{openness, conscientiousness, extraversion,
      agreeableness, neuroticism}` each on a **1-7** scale
    - `confidence`: `"low"` (<200 words), `"medium"` (200-599),
      `"good"` (600-1999), `"high"` (>=2000) — estimates converge as
      text volume grows
    - `n_words`, `confidence_note`, `model_version`, `disclaimer`
```

#### `analyze_style()`

7-dimension communication style vs executive-register norms (`POST /v1/analyses/style`).

**Args**

```text
    text: Unscripted speech or writing by ONE person (an executive's Q&A
        answers, an interview transcript, an essay). Minimum ~50 words;
        the models are calibrated on hundreds+. Scripted or multi-speaker
        text degrades validity.
```

**Returns**

```text
    `Job` with `status="queued"`. Pass it to `wait()`; the
    completed job's `.result` dict has the fields below.

    - `dimensions`: for each of intellectual_colloquial, concrete_abstract, tentative_assertive, authority_people, formal_conversational, uncertain_certain, pessimistic_optimistic →
      `{score, label, component_coverage}`. `score` is a z-score vs
      executive norms (positive = toward the second pole in the name, e.g.
      +1.0 on `uncertain_certain` = more certain than typical executives);
      `component_coverage` (0-1) = share of the dimension's lexical
      components present in the text.
    - `units`, `norms`, `n_words`, `confidence`, `model_version`,
      `disclaimer`
```

#### `analyze_texts()`

Submit many texts on one analysis line and wait for all (batch helper).

**Args**

```text
    line: `"fraud-language"` | `"personality"` | `"style"`.
    texts: Texts (one speaker each; see the single-text methods for
        minimum lengths).
    timeout: Max seconds to wait per job.
```

**Returns**

```text
    list of result dicts in input order (schema per the matching
    `analyze_*` method); `None` for a job that failed.
```

<a id="audio"></a>

## Audio analyses (single speaker)

Upload a clip of ONE speaker → vocal deception indicators, voice personality, within-speaker pair comparison, cross-speaker convergence.

_All scores are research signals intended to inform professional judgment — not diagnoses, accusations, lie detection, or determinations of fact._

#### `upload_audio()`

Upload a local audio file for analysis (3-step signed-URL flow in one call).

`POST /v1/audio` → `PUT` bytes directly to cloud storage →
`POST /v1/audio/{id}/confirm`. Bytes never transit the API gateway.

**Args**

```text
    path: Local file. WAV, MP3, M4A, FLAC, OGG accepted (decoded
        server-side to 16 kHz mono).
    content_type: MIME type matching the bytes: `"audio/wav"`,
        `"audio/mpeg"`, `"audio/mp4"`, `"audio/flac"`, `"audio/ogg"`.
```

**Returns**

```text
    `audioId` (str, UUID) for the `analyze_*` audio methods.

Audio auto-deletes **7 days** after upload, or immediately via
`delete_audio()`. Uploading costs no credits.
```

#### `analyze_deception_audio()`

Deception-relevant vocal indicators for ONE speaker's clip (`POST /v1/analyses/deception`).

**Withdrawn 2026-10-14** — raises `ModelUnavailable` (HTTP 503
`MODEL_REISSUING`, `.successor == "deception-pair-v2"`) until the
validated within-speaker self-baseline model is re-issued. The acoustic
artifact previously served on this line did not reproduce ArcheAI's
validated result and is no longer served. Best with >=10 s of continuous
speech from one speaker. Probabilistic screening signal, NOT a lie detector.

**Args**

```text
    audio_id: `audioId` returned by `upload_audio()` (status must be
        `uploaded`).
```

**Returns**

```text
    `Job`; after `wait()`, `.result` has:

    - `score` (0-1): higher = more deception-consistent vocal pattern
    - `n_seconds`: audio duration scored
    - `notes`, `model_version`, `disclaimer`

Costs 10 credits.
```

#### `analyze_voice_personality()`

Big Five from the voice alone, no transcript (`POST /v1/analyses/voice-personality`).

Verification-grade (r .46-.62 on held-out speakers, v2 artifact 2026-10-08 — top of the published
voice→personality literature); use alongside the text personality line,
not instead of it. Several minutes of one speaker is ideal.

**Args**

```text
    audio_id: `audioId` returned by `upload_audio()` (status must be
        `uploaded`).
```

**Returns**

```text
    `Job`; after `wait()`, `.result` has:

    - `traits`: `{openness, conscientiousness, extraversion,
      agreeableness, neuroticism}` on a **1-7** scale
    - `scale`, `n_seconds`, `confidence`, `notes`,
      `model_version`, `disclaimer`

Costs 10 credits.
```

#### `analyze_deception_pair()`

Which of two clips of the SAME speaker departs more toward deception (`POST /v1/analyses/deception-pair`).

Within-speaker comparison — the person is their own baseline.
**Withdrawn 2026-10-14** — raises `ModelUnavailable` (HTTP 503
`MODEL_REISSUING`, `.successor == "deception-pair-v2"`). The artifact
previously served here did not reproduce a validated result, and the
research evaluation behind the earlier paired-accuracy figure was found to
be confounded by statement length; no accuracy is claimed. A
duration-agnostic per-moment model is in development and will be re-issued
under the successor slug once it passes a speaker-held-out, leak-checked
evaluation. Both clips must be the same speaker for the result to mean
anything.

**Args**

```text
    audio_id_a: `audioId` of the first clip.
    audio_id_b: `audioId` of the second clip (must differ from A).
```

**Returns**

```text
    `Job`; after `wait()`, `.result` has:

    - `more_likely_deceptive`: `"A"` or `"B"`
    - `score_a`, `score_b` (0-1); `margin` = |score_a - score_b|
      (small margins ≈ a coin flip — report them as such)
    - `n_seconds_a`, `n_seconds_b`, `notes`, `model_version`,
      `disclaimer`
```

#### `analyze_convergence()`

Phonetic-convergence (familiarity) signal between two DIFFERENT speakers (`POST /v1/analyses/convergence`).

Research-stage, category-of-one capability: the vocal signature of prior
interaction. A lead for investigators, not proof of contact.

**Args**

```text
    audio_id_a: `audioId` of the first clip.
    audio_id_b: `audioId` of the second clip (must differ from A).
```

**Returns**

```text
    `Job`; after `wait()`, `.result` has:

    - `familiarity_signal` (float): higher = more convergence
    - `n_seconds_a`, `n_seconds_b`, `notes`, `model_version`,
      `disclaimer`
```

#### `analyze_file()`

Upload a single-speaker clip, run audio lines, wait, return results — one call.

Convenience over `upload_audio()` + `analyze_*` + `wait()`. For
MULTI-speaker recordings use `analyze_session()` instead.

**Args**

```text
    path: Local audio file (one speaker).
    lines: Analyses to run — `"voice-personality"` (default). `"deception"`
        is accepted but currently raises `ModelUnavailable` (the audio
        deception line was withdrawn 2026-10-14 and is being re-issued).
    content_type: MIME type (see `upload_audio()`).
    timeout: Max seconds to wait per line.
    delete_after: Delete the uploaded audio when done (recommended for
        sensitive material).
```

**Returns**

```text
    dict `{"audio_id": ..., "<line>": result_dict, ...}`; each result
    follows the matching `analyze_*` schema; a failed line yields
    `{"error": "<status>"}`.
```

**Raises**

```text
    ValueError: unknown line name.
    TimeoutError: a line did not finish within `timeout`.
```

#### `audio_status()`

Current status of an uploaded audio file (`GET /v1/audio/{id}`).

**Args**

```text
    audio_id: `audioId` from `upload_audio()`.
```

**Returns**

```text
    dict: `audioId`, `filename`, `status` (`pending` / `uploaded`
    / `deleted`), `contentType`, `createdAt`.
```

#### `delete_audio()`

Delete an uploaded audio file from storage immediately (idempotent).

Removes the stored audio and marks the record `deleted`. Analysis
*results* already produced from it are retained (scores, not audio). All
audio auto-deletes after 7 days regardless.

**Args**

```text
    audio_id: `audioId` from `upload_audio()`.
```

**Returns**

```text
    dict: `audioId`, `filename`, `status="deleted"`.
```

<a id="sessions"></a>

## Sessions (multi-speaker recordings)

ONE recording, SEVERAL speakers: diarization + transcription + per-speaker behavioral scores + time series. PHI mode for clinical work.

_All scores are research signals intended to inform professional judgment — not diagnoses, accusations, lie detection, or determinations of fact._

#### `analyze_session()`

Analyze ONE multi-speaker recording end-to-end (`POST /v1/analyses/session`).

For interviews, clinical/therapy sessions, meetings, depositions.
Pipeline: speaker diarization + transcription (AssemblyAI; under a BAA,
PHI mode available) → per-speaker communication style and Big Five via
the production text models → per-speaker behavioral time series →
optional per-speaker vocal deception on speaker-isolated audio. No LLM is
involved at any step.

**Args**

```text
    path: Local recording with several speakers.
    mime_type: MIME type (`"audio/wav"`, `"audio/mpeg"`, `"audio/mp4"`,
        `"audio/flac"`, `"audio/ogg"`).
    lines: Outputs to produce — any subset of `"transcript"`,
        `"speakers"`, `"style"`, `"personality"`, `"segments"`,
        `"deception"`. Default: all except `"deception"`.
    include_deception: Shortcut that adds `"deception"` to `lines`. Off
        by default; in clinical settings consider leaving it off.
    phi: PHI mode — redacts PII/PHI in the transcript (person names, phone,
        email, location, date of birth, SSN, medical condition, drug,
        injury, blood type, medical procedure, healthcare numbers, card
        numbers) and deletes the transcript from the transcription vendor
        after retrieval. Use within a BAA-covered workspace.
    redact_policies: Override the redaction policy list (AssemblyAI policy
        names, e.g. `"person_name"`, `"medical_condition"`, `"drug"`,
        `"location"`, `"date_of_birth"`). Implies redaction even when
        `phi` is False.
    language_code: ISO code for transcription (default `"en"`).
    delete_after: Delete the uploaded audio when done.
    timeout: Max seconds to wait for the job (default 3600).
    poll_s: Seconds between status polls.
    on_status: Optional callback `fn(status, stage)` called on each poll
        with `"queued"` / `"processing"` / `"completed"` / `"failed"`.
```

**Returns**

```text
    `SessionResult`:

    - `status` (`"completed"`, `"failed: ..."` or `"timeout ..."`),
      `job_id`, `upload_id` (the audioId)
    - `transcript`: utterances `{speaker, start_ms, end_ms, text,
      n_words}`; speakers are labelled `"A"`, `"B"`, ...
    - `behavioral_speakers`: per speaker `{speaker, n_utterances,
      n_words, speaking_time_s, style{dimensions...},
      personality{traits...}}` — style/personality omitted (with a
      `note`) for speakers under 50 words
    - `behavioral_segments`: per-speaker sliding windows of 6 utterances
      `{speaker, start_ms, end_ms, n_words, style{dim: z}}` — the
      behavioral time series
    - `deception_statements` (only if requested): per speaker
      `{speaker, audio_seconds, deception_score, model_version}`
    - `statistics`: `{n_speakers, n_utterances, duration_s,
      processing_s, transcription{provider, redacted, phi_mode},
      model_version}`
    - `raw`: the full result dict
    - `.speaker_frame()` / `.segment_frame()` → pandas DataFrames

Costs 40 credits per session (covers vendor transcription).
```

**Raises**

```text
    ArcheAPIError: `BAD_REQUEST` (bad line / audio not uploaded),
        `INSUFFICIENT_CREDITS`, trial codes.
```

<a id="jobs"></a>

## Jobs, account, models

Polling, credits, model catalog.

_All scores are research signals intended to inform professional judgment — not diagnoses, accusations, lie detection, or determinations of fact._

#### `job()`

Fetch the current state of an analysis job (`GET /v1/jobs/{id}`).

**Args**

```text
    job: a `Job` or job-id string.
```

**Returns**

```text
    `Job` with `status` in `queued` / `processing` /
    `completed` / `failed`, `result` populated when completed.
```

#### `wait()`

Poll a job until it completes or fails.

**Args**

```text
    job: a `Job` or job-id string.
    timeout: Max seconds to wait (text jobs finish in seconds, audio in
        ~10-60 s, sessions in 20 s to several minutes).
    interval: Seconds between polls.
```

**Returns**

```text
    The final `Job` (`completed` or `failed`; inspect
    `.result`).
```

**Raises**

```text
    TimeoutError: still running after `timeout` seconds.
```

#### `account()`

Account status for this API key: credits and (if any) trial terms.

**Returns**

```text
    dict: `userId`, `apiKeyId`, `keyPrefix`, `credits` (remaining
    workspace balance), `createdAt`, `lastUsedAt`, and — trial
    workspaces only — `trial` = `{label, allowedTickers, historyFrom,
    historyMonths, maxHistoryCallsPerDay, expiresAt, expired}`.
```

#### `models()`

List the models this key can invoke and what each costs.

**Returns**

```text
    DataFrame / list of dicts, one row per model: `modelSlug`, `name`,
    `description`, `creditsCost`, `isActive`, access info.
```

#### `model()`

Details for one model by slug (`GET /v1/models/{slug}`).

**Args**

```text
    slug: `modelSlug` as listed by `models()` (e.g.
        `"fraud-language-v1"`, `"behavioral-history-v1"`).
```

**Returns**

```text
    dict: `modelSlug`, `name`, `description`, `creditsCost`,
    `isActive`, `keyHasPermission` (whether this key may invoke it —
    tier-3 key grant only; workspace grants and default access are
    reflected in `models()`).
```

**Raises**

```text
    ArcheAPIError: `NOT_FOUND` for an unknown slug.
```

<a id="interpreting-scores"></a>

## Interpreting scores

Every ArcheAI output is a **research signal**: a measurement of how speech
reads against a validated reference, meant to inform professional judgment.
None is a diagnosis, an accusation, a lie detector, or a determination of
fact. This page explains what each number means and how far to trust it.

### Communication style (7 dimensions) — z-scores

Each dimension is a **z-score against executive-register norms** computed
on the research corpus. 0 = typical executive; +1 = one standard deviation
toward the *second* pole in the dimension's name; −1 = toward the first.

| dimension | first pole ← → second pole |
|---|---|
| `intellectual_colloquial` | intellectual ← → colloquial |
| `concrete_abstract` | concrete ← → abstract |
| `tentative_assertive` | tentative ← → assertive |
| `authority_people` | authority-oriented ← → people-oriented |
| `formal_conversational` | formal ← → conversational |
| `uncertain_certain` | uncertain ← → certain |
| `pessimistic_optimistic` | pessimistic ← → optimistic |

Rules of thumb: |z| < 0.5 is unremarkable; |z| > 1 is a notable departure;
|z| > 2 is rare (~2% of executive speech). The norms are executive speech —
other registers (casual conversation, clinical sessions) will sit off-centre
*as a population*; compare within your own sample or to a speaker's own
history rather than to 0 in those settings. `component_coverage` < 0.5
means the text had few of the dimension's lexical markers; treat the score
as low-confidence.

**The most informative use is change over time.** A speaker's deviation
from *their own* baseline (what `behavioral_history()` gives you for
executives, and what session `segments` give you within a recording) is
ArcheAI's proprietary self-baseline construct and carries more signal than
any absolute level.

### Big Five (text and voice) — 1 to 7

`openness`, `conscientiousness`, `extraversion`, `agreeableness`,
`neuroticism` on a 1–7 scale (4 = midpoint). The **text** line serves the
lexicon + structural student: held-out r .64–.70 vs the peer-reviewed
reference across all speakers, .82–.89 for speakers with 10K+ words; the
served artifact (sha256 in every response) is the number you get, and the
research-corpus profiles use a separate three-expert model. The **voice-only** model is verification-grade
(r .46–.62 on held-out speakers, v2 artifact) — use it to corroborate the
text estimate, not alone. Text `confidence` tiers
(`low` < 200 words → `high` ≥ 2000) tell you how settled the estimate is.
These measure *expressed* personality in a context; they are not clinical
instruments.

### Fraud-language score — 0 to 1

Probability-like output of a mixture-of-experts model trained on executive
Q&A speech from SEC-enforcement eras. Higher = more similar to
enforcement-era speech patterns. It flags *speech that reads like*
historically problematic speech; it says nothing about the truth of any
statement. `experts.deviation` is `None` for one-shot text — the strongest
expert needs a speaker's history, which only the research corpus provides.
That is why the result carries two AUCs, both on the same company-held-out
split: `holdout_auc` **0.741** for the single-text path that produced your
score, and `holdout_auc_full_model` **0.874** for the full three-expert
model used at corpus scale.

### Vocal deception indicators — 0 to 1

Acoustic-feature model validated on incentivized revealed-truth corpora.
Three cautions: (1) it measures *vocal patterns associated with* deception
under those conditions, not lying; (2) ≥10 s of one speaker's speech is
needed; (3) the **within-speaker pair** version (`analyze_deception_pair`)
is the more defensible use because the speaker is their own baseline —
but both lines are **withdrawn** (503 `MODEL_REISSUING`): the artifact
that had been served did not reproduce a validated result, and the
earlier paired-accuracy evaluation was found to be confounded by
statement length, so no accuracy is claimed. A duration-agnostic
per-moment model is in development and returns under
`deception-pair-v2` once it passes a speaker-held-out, leak-checked
evaluation. Until then, use the fraud-language text score as the
shipped deception-adjacent signal.

What deception signals can and can't detect: they track a speaker's
relationship to their *own* statement in the moment. Chronic, normalized
behavior the speaker doesn't experience as lying produces no departure
from baseline — so an unflagged denial and a true accusation can coexist.

### Convergence / familiarity — unbounded float

Phonetic-convergence signal between two speakers (wav2vec2
representations). Higher = more convergence, consistent with prior
interaction. Research-stage; a lead, not evidence.

### Sessions

Per-speaker scores use the text models above on each speaker's
transcribed speech (minimum 50 words/speaker). `segments` are sliding
windows of 6 utterances → a style time series; look for *within-session
drift* rather than absolute levels. Diarization labels (`A`, `B`, …) are
positional, not identities.

<a id="errors"></a>

## Errors

All API errors raise `arche_ai.ArcheAPIError` with:

| attribute | meaning |
|---|---|
| `.status` | HTTP status code |
| `.code` | machine-readable code (below) |
| `.message` | human-readable explanation |
| `.meta` | dict of extra detail (e.g. `required`/`available` credits, `expiresAt`) |
| `.is_trial_limit` | `True` for the three `TRIAL_*` codes |

```python
from arche_ai import ArcheAPIError
try:
    df = c.behavioral_history("MSFT")
except ArcheAPIError as e:
    if e.is_trial_limit:
        ...  # outside the agreed trial scope — not a permissions problem
    elif e.code == "INSUFFICIENT_CREDITS":
        print(e.meta)  # {"required": 5, "available": 2}
    else:
        raise
```

| code | HTTP | when | what to do |
|---|---|---|---|
| `UNAUTHORIZED` | 401 | missing/invalid/revoked key | check the key; keys are workspace-scoped |
| `FORBIDDEN` | 403 | key lacks permission for this model | request access (model tiers are admin-granted) |
| `TRIAL_TICKER_DENIED` | 403 | ticker not on the trial's allow-list | request the ticker be added, or stay in scope |
| `TRIAL_DAILY_CAP` | 403 | daily `behavioral_history` cap reached | resets 00:00 UTC |
| `TRIAL_EXPIRED` | 403 | trial expiry passed — blocks **every** endpoint | contact ArcheAI to extend/convert |
| `INSUFFICIENT_CREDITS` | 402 | workspace balance below the model's cost | `meta.required` / `meta.available`; top up in the web app |
| `BAD_REQUEST` | 400 | invalid body (text too short, bad `lines`, audio not confirmed) | `meta.fieldErrors` when present |
| `NOT_FOUND` | 404 | unknown ticker / audio id / job id / inactive model | — |
| `MODEL_REISSUING` | 503 | the model was withdrawn and is being re-issued under a successor slug — raised as `ModelUnavailable` (subclass of `ArcheAPIError`) with `.successor` | retry with `.successor` once `models()` lists it; nothing else is affected |
| `INTERNAL_SERVER_ERROR` | 500 | server fault | retry; report with the job id if persistent |

Job-level failures (the job was accepted but processing failed) do **not**
raise — `wait()` returns a `Job` with `status="failed"`; the gateway's
`error` field is available via `client.job(job_id)`. Credits for failed
jobs are reviewed and refunded by support.

`TimeoutError` (standard library) is raised by `wait()` / `analyze_file()`
when a job is still running after `timeout` seconds — the job itself
continues; poll again with `client.job(job_id)`.
