Firm data SDK, CLI and MCP
Read stored LegalVoice and MediVoice firm records through the existing customer API. The FAB's Claude Agent SDK tools call the same backend query functions; this package gives scripts and external MCP hosts access through firm API keys.
Official developer documentation: LegalVoice and MediVoice. The voice-firm-data package contains the Python SDK, command-line client, and optional FastMCP server. It contains no API keys, customer records, database credentials, or backend application code.
Install and authenticate
Use Python 3.10 or later:
python -m venv .venv-firm-data
source .venv-firm-data/bin/activate
python -m pip install --upgrade pip
python -m pip install "voice-firm-data[mcp]==0.3.1"
Omit [mcp] if you only need the Python client and CLI. Developer settings also
provide a versioned wheel download and checksum. On Windows, activate the
virtual environment with .venv-firm-data\Scripts\activate.
Create a data export key in the product's Developer API settings. Set it privately as FIRM_DATA_API_KEY, LEGALVOICE_API_KEY, or MEDIVOICE_API_KEY. Resolution follows that order. Keys need data:read or exports:read; the server derives the firm from the key. A key cannot select a different firm through query arguments.
An API key is required; an OAuth client ID is not. The key identifies the
firm (account_id, also called agency_id). The API's client_id identifies a
person/client record inside that firm; use it as an optional filter when you
want that client's calls, notes, tasks, or web chats. It is not a password and
cannot grant access to another firm's records. firm-data whoami reports the
account and key identity; firm-data query clients discovers client records.
Set FIRM_DATA_BASE_URL to the API origin shown in Developer settings. It defaults to https://api.legalvoice.app, the shared API default. Use the API origin, not the dashboard's domain. HTTPS is required except for localhost development. Credentials are not accepted as CLI arguments, returned in errors, or forwarded across redirects.
CLI
firm-data whoami
firm-data discover
firm-data query clients --search Jane
firm-data query members
firm-data query web-chats --filter client_id=CLIENT_ID
firm-data query web-chat-messages --filter widget_key=WIDGET_KEY --filter session_id=SESSION_ID
firm-data query web-chat-tools --filter widget_key=WIDGET_KEY --filter session_id=SESSION_ID
firm-data query activity --filter client_id=CLIENT_ID
firm-data query calls --since 2026-09-01 --until 2026-09-08 --filter callback_requested=true
firm-data count calls --since 2026-09-01 --until 2026-09-08 --group-by status
firm-data query tasks --filter status=open --date-field due_on --until 2026-09-15
firm-data query notes --filter client_id=CLIENT_ID
firm-data query profile-notes --search Jane
firm-data query calls --search 'car accident'
firm-data get calls CALL_ID_OR_SID
firm-data query notes --all > notes.jsonl
Single requests print JSON with source references and coverage metadata. Lists return has_more and next_cursor; pass --cursor CURSOR with the same filters. Grouped counts also paginate. Ungrouped count returns the exact count of all matches, regardless of --limit.
query --all streams JSONL through every page (default ceiling: 1,000 pages; configurable with --max-pages). It exits nonzero on errors, repeated/missing cursors or an exceeded page ceiling. A file written before an error is incomplete; check the exit status. Pages are live reads, so concurrent inserts or updates can shift pagination. This is not a transactional snapshot.
Dates are UTC. since is inclusive and until exclusive. For September 1–7, use --since 2026-09-01 --until 2026-09-08. Timestamps require an explicit timezone offset. Exact filters and grouping fields vary by resource; discovery lists the supported fields for the deployed schema. Search is literal, case-insensitive substring matching over listed fields, including call transcripts.
Python SDK
from voice_firm_data import FirmDataClient
with FirmDataClient.from_env() as firm:
identity = firm.identity() # account_id, scopes and enforced key/account budgets
catalog = firm.discover()
total = firm.query("calls", mode="count", since="2026-09-01")
tasks = firm.query("tasks", filters={"status": "open"})
for note in firm.iter_records("notes", filters={"client_id": "CLIENT_ID"}):
print(note["body"], note["source_ref"])
call = firm.get("calls", "CALL_ID") # full transcript/content
An explicit client is FirmDataClient(api_key, base_url). Close it or use a context manager. Queries default to 25 records, maximum 250. Transcripts, metadata and document payloads are omitted from list results unless include_full_payload=True; each result lists omitted_fields. Detail requests include full content. Sensitive configuration resources always use a limited metadata projection, even in full mode.
IDs and relationships
| Field | Meaning |
|---|---|
account_id |
The firm's existing agency_id, determined by the authenticated key |
client_id |
The stable agency_people.id returned by clients and people; changing a name or phone does not change it |
member_id |
A firm membership's agency_members.id |
user_id |
The member's login identity; one login can belong to multiple firms |
client_id is an alias for an existing identity, so there is no new ID backfill. Related records expose it when person_id or primary_person_id is recorded. An unlinked record has client_id: null; clients are never joined by guessing from names or phone numbers. Discovery reports each resource's client_id_field and whether it supports the client_id filter. Other relationships use the catalog's specific IDs, such as call_sid, engagement_id, run_id, and document_id.
Enforced API budgets
All developer-key requests share PostgreSQL counters across server replicas. A reservation checks and charges both the key and its account atomically, before the handler runs. Creating or rotating keys does not reset the account budget. Existing keys automatically receive the standard limits.
| Budget | Per UTC minute | Per UTC day | Concurrent requests |
|---|---|---|---|
| Standard key | 120 units | 12,000 units | 3 |
| Limited key preset | 30 units | 3,000 units | 3 |
| Account, shared by all keys | 600 units | 60,000 units | 8 |
Ordinary requests cost 1 unit, queries/counts 5, discovery/export manifests 10, and CSV exports 60. These are fixed UTC minute/day windows, not rolling windows. Customer data requests have a 90-second deadline; query execution also has a 30-second database timeout. Short leases recover concurrency capacity after a worker stops unexpectedly.
Owners/admins choose a standard or limited budget when creating a key in Developer API settings. The key-creation endpoint also accepts rate_limits: {"per_minute": 30, "per_day": 3000}. Custom positive values cannot exceed the server's standard caps, including when reading stored metadata. A request costing more than a key's entire minute/day budget returns 422; limited keys must use paginated JSON instead of CSV.
An exhausted budget returns 429 with Retry-After in seconds. Responses expose Limit, Remaining, Reset (Unix seconds), Day-Limit, Day-Remaining, Account-Limit, Account-Remaining, and Cost, all prefixed with X-RateLimit-. GET /me / firm-data whoami reports the configured limits. Failed quota storage returns 503 without executing the request. Requests rejected for missing/revoked keys do not reserve a budget; authenticated requests consume units even if their handler rejects the request.
The SDK, CLI, and MCP adapter retry 429 responses up to twice, honoring delays up to 60 seconds. A longer delay, or exhausted retries, raises FirmDataRateLimitError with status_code=429 and retry_after; the CLI exits nonzero. Python callers can set max_retries=0 to handle retries themselves and inspect client.last_rate_limits. Multi-page exports retain their incomplete-file behavior on quota exhaustion.
MCP
Install the mcp extra, then configure your MCP host to run firm-data-mcp over stdio. The server uses the standalone FastMCP framework, pinned to 4.0.3, with from fastmcp import FastMCP and decorated tools. FastMCP owns schema generation, validation, transport and protocol handling. This package exposes local stdio transport; there is no hosted MCP URL or OAuth connector in this release.
{
"mcpServers": {
"firm-data": {
"command": "/absolute/path/to/.venv-firm-data/bin/firm-data-mcp",
"env": {
"FIRM_DATA_BASE_URL": "https://api.legalvoice.app",
"FIRM_DATA_API_KEY": "<set your firm key privately in the host>"
}
}
}
}
Use the host's private environment/secret settings where available. The placeholder must be replaced; it is not automatic shell-variable interpolation. GUI apps may require the executable's absolute path. The server writes protocol traffic to stdout and does not open a listening network port.
The three tools are:
| Tool | Purpose |
|---|---|
discover_firm_data |
Available resources, search/filter/date/group fields and query schema |
query_firm_data |
Search, filter, paginate or count all matching records |
get_firm_record |
Full record details with a source reference |
Try: “Show overdue open tasks grouped by owner,” “Find calls mentioning a car accident,” “Which members handle this client's work?” or “Summarize this client's web chats, notes and open tasks.” Resolve the client through clients, then use its client_id. For web chat transcripts and tool activity, query web-chat-messages and web-chat-tools with both widget_key and session_id from web-chats.
HTTP contract and coverage
All routes use the existing customer API key authentication and scopes:
GET /api/v1/customer/v1/data— discovery.GET /api/v1/customer/v1/me— account, key scopes and budgets.POST /api/v1/customer/v1/query— a read-only query body, e.g.{"resource":"tasks","filters":{"status":"open"}}.GET /api/v1/customer/v1/records/{resource}/{record_id}— record detail.
The registry covers 67 named resources (including aliases). Some tables are optional and differ by deployment; discovery reports availability explicitly. Requests to unavailable resources fail instead of claiming zero records. Each registered resource also has list, detail, and CSV export routes in the Developer API catalog.
| Area | Resources |
|---|---|
| Clients and members | Clients/people, canonical contacts and cases, members/users, identity links, client facts, client messages, client audit history |
| Communications and intake | Calls/transcripts, intakes, web intakes, web chat sessions/messages/tool calls, email, SMS, messaging agent sessions/turns, appointments |
| Work and activity | Saved notes, profile notes, tasks, client activity, engagements/participants/links/events, work items and their events |
| Documents | Source/generated documents, projects, annotations, mentions, document workflows, e-sign envelopes/packages, knowledge sources/text chunks, extraction templates/runs |
| Automation and campaigns | Automations/dispatches, workflow definitions/runs/node runs/events/artifacts/human tasks, campaigns/runs/recipients/events/timeline |
| Administration | Phone numbers, departments, agent profiles/configuration, intake forms, integration metadata, webhook delivery history, audit events, usage and billing periods |
Activity and event feeds expose persisted history. They do not imply that every historical UI click or unrecorded action was captured, and this release does not add a universal activity recorder.
notes contains durable team notes; profile-notes contains nonempty agency_people.metadata.contact_notes. These are separate feeds, matching the two sources in the dashboard. people contains dashboard person IDs; contacts uses the canonical contact store when present and falls back to people only when that table is absent. Do not add overlapping resource counts together as unique clients or intakes.
These tools expose registered, stored firm data, not arbitrary database tables, SQL, provider credentials, or every record in an external CRM. Live external case data continues through the existing connected CRM tools. The FAB's chat controls can disable the new tools with the firm-data/recent-context groups; they remain available in review-only mode because they do not mutate records.
Distribution and license
Install from PyPI or the versioned download in Developer settings. The package
uses a proprietary SDK license, not an open-source license. Authorized
commercial integrations are permitted under the included LICENSE; standalone
redistribution and publication of modified SDK versions require written
permission. Third-party dependencies keep their own licenses.
The SDK sends requests only to the API origin configured by the developer or MCP host. Use an API origin you trust: the configured origin receives your firm API key. HTTPS certificate verification is enabled; redirects and environment proxy discovery are disabled. No telemetry is implemented by this SDK.
The MCP host may send returned firm data to its selected model provider. Use a host and provider approved for the data you access. Model instructions cannot supply a different API key, account, or API origin through these tools.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file voice_firm_data-0.3.1.tar.gz.
File metadata
- Download URL: voice_firm_data-0.3.1.tar.gz
- Upload date:
- Size: 11.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ebe9ce0cb02a9dd48176767a53f1c047f44bdef36007636ac782723d99f054aa
|
|
| MD5 |
a7d32228eb57cebee1e9291c95485488
|
|
| BLAKE2b-256 |
a9a35d4f0aa0721644ce48833caff248ff3065a45712907b898c9bd27c8397f2
|
File details
Details for the file voice_firm_data-0.3.1-py3-none-any.whl.
File metadata
- Download URL: voice_firm_data-0.3.1-py3-none-any.whl
- Upload date:
- Size: 13.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d8cdfb84413f55300d4c65aa1f3787fe3a770279bf5659484910fd1309222a8
|
|
| MD5 |
476400121e3081177ca11fc626bd3dbb
|
|
| BLAKE2b-256 |
1521544d516527b9139b66fc98a5b101adc676d31e02d78cc17ac60face78922
|