Typed client (docgen_client)¶
The product-owned, authoritative async client for DocGen's /v1 REST API —
what consumers (client-site, client-rwdi, client-odyssey, client-tomorrowlaw)
import instead of hand-rolling an HTTP client against the /v1 contract. It is a
standalone distribution (docgen-client, top-level module docgen_client)
depending only on httpx + pydantic; importing it never pulls in the DocGen
server's dependencies (altaforge, sqlalchemy, fastapi).
The reference below is generated directly from the client's source docstrings by mkdocstrings — so it cannot drift from the code. Only the public client package is documented here; DocGen's internal server modules are deliberately not part of the public reference.
docgen_client ¶
The authoritative typed client for DocGen's /v1 REST API.
from docgen_client import DocGenClient
Consumers (client-site, client-rwdi, client-odyssey, client-tomorrowlaw)
import this instead of hand-rolling their own HTTP client against DocGen's
/v1 contract. This is a standalone distribution (docgen-client, top-level
module docgen_client) depending on only httpx + pydantic — see
_client.py's module docstring for the auth wiring, the per-call tenant
scope, the SSE deadline fix, and the import-safety guarantee, and this
package's README.md for why it is a separate distribution rather than
docgen.client. See
docs/superpowers/specs/2026-07-25-shared-docgen-client-design.md in
altaml/client-site for the cross-repo migration plan.
DocGenClient ¶
DocGenClient(base_url, token_provider, default_org_slug=None, *, transport=None, poll_timeout_s=_DEFAULT_POLL_TIMEOUT_S, poll_interval_s=_DEFAULT_RECONNECT_BACKOFF_S)
Async HTTP client for DocGen's /v1 REST contract.
Configure endpoint + token source, and an optional default tenant.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_url
|
str
|
DocGen's base URL. Empty string = unavailable (see
|
required |
token_provider
|
TokenProviderLike
|
a bearer-token source, called per request. |
required |
default_org_slug
|
str | None
|
the tenant sent as |
None
|
transport
|
AsyncBaseTransport | None
|
injected in tests ( |
None
|
poll_timeout_s
|
float
|
default budget for |
_DEFAULT_POLL_TIMEOUT_S
|
poll_interval_s
|
float
|
backoff between the one reconnect
|
_DEFAULT_RECONNECT_BACKOFF_S
|
__aenter__
async
¶
__aenter__()
Async-context-manager support: async with DocGenClient(...) as client:.
__aexit__
async
¶
__aexit__(*exc_info)
Release the connection pool on context exit, success or failure.
list_templates
async
¶
list_templates(*, org_slug=None, bearer_token=None)
Return every template document in the org (GET /v1/templates).
Returns the raw templates list — open shape, same rationale as
get_document/check_readiness (see models.py's module docstring).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bearer_token
|
BearerTokenLike | None
|
overrides the constructor's token provider for
this call — a |
None
|
get_template
async
¶
get_template(slug, *, org_slug=None, bearer_token=None)
Resolve one template by its stable, org-unique slug
(GET /v1/templates/{slug}, #346) — preferred over a title match,
which breaks on rename.
Returns the raw document dict. Org-scoped: a slug that exists only
in another tenant is a 404, surfaced here as DocGenError.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bearer_token
|
BearerTokenLike | None
|
overrides the constructor's token provider for
this call — a |
None
|
create_template_from_spec
async
¶
create_template_from_spec(spec, *, dry_run=False, org_slug=None, bearer_token=None)
Compile a declarative template spec into the org (POST /v1/templates).
The spec is the JSON body — the documented headless authoring surface,
so a consumer bootstraps its house template with no product-side seed.
Idempotent: existing keys skip-with-warning. dry_run=True validates
the spec without persisting. Returns the compile result + template.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bearer_token
|
BearerTokenLike | None
|
overrides the constructor's token provider for
this call — a |
None
|
update_template_spec
async
¶
update_template_spec(document_id, spec, *, org_slug=None, bearer_token=None)
Apply a spec to update an existing template in place
(PUT /v1/documents/{id}/spec).
The update-in-place counterpart to create_template_from_spec, which
is create-only (existing keys skip-with-warning) and so cannot
converge a template that already exists. Sections/fields/data-needs/
config and the org entities the spec redefines are synced to match
the spec — the read-modify-write loop is GET …/spec -> edit ->
update_template_spec. document_id is the template's document id
(resolve it via get_template/list_templates first — this call
takes the id, not the slug). Returns the applied document.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bearer_token
|
BearerTokenLike | None
|
overrides the constructor's token provider for
this call — a |
None
|
list_documents
async
¶
list_documents(*, org_slug=None, bearer_token=None)
List the caller-org's document instances (GET /v1/documents).
Returns the raw documents list — open shape, same rationale as
list_templates (see models.py's module docstring).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bearer_token
|
BearerTokenLike | None
|
overrides the constructor's token provider for
this call — a |
None
|
list_texts
async
¶
list_texts(*, org_slug=None, bearer_token=None)
List the org-configured Texts / phrase atoms (GET /v1/texts).
list_snippets
async
¶
list_snippets(*, org_slug=None, bearer_token=None)
List the org-configured Snippets / clause molecules (GET /v1/snippets).
list_risk_blocks
async
¶
list_risk_blocks(*, org_slug=None, bearer_token=None)
List the org-configured Risk Blocks (GET /v1/risk-blocks).
Unwraps the riskBlocks key (DocGen returns this one camelCase).
list_orchestration_rules
async
¶
list_orchestration_rules(*, org_slug=None, bearer_token=None)
The orchestration rulebook — what binds which molecule
(GET /v1/orchestration-rules).
create_document
async
¶
create_document(template_id, title, fields=None, *, org_slug=None, bearer_token=None)
Instantiate a template into a new document (POST /v1/documents).
Returns the raw response body ({'document': {...}}) — DocGen
documents itself as an open shape (see models.py's module
docstring), so this is a pass-through, not a typed model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
org_slug
|
str | None
|
tenant override for this call; defaults to the
client's |
None
|
bearer_token
|
BearerTokenLike | None
|
overrides the constructor's token provider for
this call — a |
None
|
orchestrate
async
¶
orchestrate(document_id, source_text, *, org_slug=None, bearer_token=None)
Run the registered orchestrator over source_text
(POST /v1/documents/{id}/orchestrate).
Returns the review payload (plan, bound molecules, entity names, set field values) as a raw dict.
generate
async
¶
generate(document_id, variables=None, *, force=False, preserve_edited=False, org_slug=None, bearer_token=None)
Fire the render pipeline (POST /v1/documents/{id}/generate).
Fire-and-return — completion arrives via watch_progress. A document
with hand-edited sections and neither force nor preserve_edited
set is refused with a 409, surfaced here as a terminal DocGenError
(never retried blindly — the caller must re-issue with an explicit
choice, per DocGen's confirm-before-overwrite gate).
watch_progress
async
¶
watch_progress(document_id, *, timeout_s=None, reconnect_backoff_s=None, org_slug=None, bearer_token=None)
Watch GET /v1/documents/{id}/progress (SSE) until a terminal
event, or timeout_s elapses.
Terminal events: pipeline_finished, pipeline_failed,
pipeline_blocked — a hard readiness block emits pipeline_blocked
and the pipeline returns without ever emitting finished/failed, so
waiting past it is a guaranteed timeout.
The SSE stream can end without a terminal event (idle timeout, a
transient network drop) with no error — reconnect once after
reconnect_backoff_s before giving up. A genuine deadline miss
(including a silent stream that never emits at all — the bug this
client's asyncio.timeout_at wrapper exists to fix) is
DocGenTransientError and is never retried within this call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout_s
|
float | None
|
total wall-clock budget for the ENTIRE call —
the initial connection AND the one reconnect share it. Computed
once into a single absolute budget
( |
None
|
get_document
async
¶
get_document(document_id, *, org_slug=None, bearer_token=None)
One document: header + sections (GET /v1/documents/{id}).
Returns {'document': {...}, 'sections': [...]} as a raw dict.
edit_section_output
async
¶
edit_section_output(document_id, section_id, content, *, org_slug=None, bearer_token=None)
Push a hand-edited section output to DocGen
(PUT /v1/documents/{id}/sections/{section_id}/output).
Marks the section edited on DocGen's side and records a new
section-output version, so a subsequent force-less generate never
silently overwrites it.
check_readiness
async
¶
check_readiness(document_id, *, org_slug=None, bearer_token=None)
Run DocGen's pre-generation readiness gate without generating
(GET /v1/documents/{id}/readiness).
Returns {generation_should_proceed, ui_state, gaps}. Informational
only; a genuinely blocking failure still surfaces via the render
pipeline's own pipeline_blocked event.
export_docx
async
¶
export_docx(document_id, *, org_slug=None, bearer_token=None)
Export a rendered document as .docx
(GET /v1/documents/{id}/export.docx).
The route returns the binary .docx directly (not base64 JSON), with
the filename in Content-Disposition. Returns (filename, content).
TokenProvider ¶
Bases: Protocol
Structural match for altaforge.auth.client.ServiceTokenProvider.
Only .token() is used; the provider owns caching and
refresh-before-expiry internally, so it is called once per request. A
bare Callable[[], str] also satisfies TokenProviderLike (see below) —
the client dispatches on which shape it was given.
DocGenError ¶
Bases: RuntimeError
Raised when DocGen returns an unexpected response.
Terminal by default: a caller should not suggest retrying without other
evidence, since most of these come from DocGen's own pipeline_failed /
pipeline_blocked events, which carry only a message string (no exception
type) — see DocGenTransientError for the cases we CAN tell are worth a
retry.
DocGenTransientError ¶
Bases: DocGenError
A DocGen failure a retry might resolve.
Narrowly scoped — deliberately NOT every 5xx:
- HTTP 429/502/503/504 (typically transient upstream load).
- An
httpx.TransportError(connection refused/reset, DNS failure, or a connect/read/write/pool timeout) — the request never got a response at all, so retrying with the same inputs is safe to attempt.
A bare HTTP 500 stays a terminal DocGenError: DocGen maps an unhandled
server exception (a permanent bug or config error, e.g. a missing
setting) to a plain 500 with no distinguishing status code — treating it
as transient would tell a caller to retry a request that will never
succeed. This is not a hypothetical: SITE hit exactly this incident when
an earlier classification treated all 5xx as transient.
DocGenUnavailableError ¶
Bases: DocGenError
Raised when no DocGen endpoint is configured (empty base_url).
CreateDocumentRequest ¶
Bases: BaseModel
Mirrors api_v1.CreateDocumentBody field-for-field — POST /v1/documents.
Deliberately no snake_case alias: this model is write-only (built and
immediately serialized inside DocGenClient.create_document, never parsed
from JSON), so there is no ergonomic benefit to a Python-side snake_case
name — and a bare field name here, rather than Field(alias=...), sidesteps
a real mypy wrinkle: pydantic's BaseModel uses PEP 681
dataclass_transform, which mypy honors by synthesizing the constructor
from each field's alias regardless of a runtime-only populate_by_name
setting. Aliasing bought nothing here and cost a snake_case construction
call site that wouldn't type-check under mypy --strict.
GenerateRequest ¶
Bases: BaseModel
Mirrors api_v1.GenerateBody field-for-field — POST /v1/documents/{id}/generate.
Bare preserveEdited field name (no snake_case alias) for the same reason
as CreateDocumentRequest — write-only, and aliasing fights
mypy --strict's dataclass_transform-based constructor synthesis for no
ergonomic gain.
GenerateResponse ¶
Bases: BaseModel
Mirrors api_v1.GenerateResponse — the generate 200 body.
OrchestrateRequest ¶
Bases: BaseModel
Mirrors api_v1.OrchestrateBody — POST /v1/documents/{id}/orchestrate.
Already snake_case on the wire (unlike the top-level camelCase DTOs) —
matches api_v1.py's own field name exactly, no alias needed.
ProgressEvent ¶
Bases: BaseModel
One terminal SSE event from GET /v1/documents/{id}/progress.
event is the only field every terminal event is guaranteed to carry
(pipeline_finished / pipeline_failed / pipeline_blocked —
docgen.progress.TERMINAL_EVENTS); the rest of the payload varies by event
type (e.g. pipeline_failed carries an error message, pipeline_blocked
carries gap detail), so extra keys are allowed through rather than
enumerated — DocGen's own progress payload is not a fixed schema either
(see docgen.progress.emit). seq, when present, lands in model_extra
like any other extra key; _sse.read_progress_stream reads it from the
raw event dict (not this model) to dedup replayed events across a
reconnect — see that module's docstring.
SectionOutputRequest ¶
Bases: BaseModel
Mirrors api_v1.SectionOutputBody — PUT …/sections/{id}/output.