---
title: "Mcp Render"
method: POST
path: "/api/v1/mcp/render"
tags: ["mcp"]
---

# Mcp Render

`POST /api/v1/mcp/render`

## Request

- Base URL: `https://api.civilquants.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.civilquants.com/api/v1/mcp/render`
- Auth: none declared

Render the supplied compute_results into the chosen deliverable format.

Free-tier callers consume daily quota per launch spec § 3.6 alongside
/api/v1/mcp/compute calls — render is computationally heavier than compute, so
bracketing it under the same quota is essential. Paid callers bypass.

Validation order is deliberate: skill validity → paid-only tier gate →
rate-limit consumption. The tier gate runs FIRST so a free caller hitting
the paid Word skill is rejected at the gate without burning daily quota
(closes the IP-based quota-exhaustion DoS where an attacker drains a
victim's free quota by repeatedly POSTing the paid skill from the
victim's IP — security-reviewer 2026-05-15 finding).

## Headers

- `authorization` string, nullable

## Request body

- RenderRequest: Wire shape for POST /api/v1/mcp/render.
  - `skill` string, required: One of ['civilquants/qs_boq_export', 'civilquants/qs_boq_word_report']. Word skill ('civilquants/qs_boq_word_report') is paid-only; Excel skill is open to free callers with sheet protection + watermark.
  - `compute_results` ComputeResult[], required: Up to 100 ComputeResult entries per call — comfortably above the 46-assembly v1 catalogue. Caps in-memory render at a sane ceiling to defend against denial-of-wallet from a compromised paid token.
    - `assembly_slug` string, required
    - `standard` 'CESMM4' | 'NRM2' | 'SMM7' | 'MMHW', required
    - `parameters_used` ParametersUsed, required: Schema § 1.2 — the parameter snapshot the engine actually used. Splits user-supplied values from engine defaults so customers can see which numbers came from them and which the engine filled in. Every defaulted parameter SHOULD have a corresponding entry in `assumptions` — the bridge layer enforces this when wrapping engine output.
      - `user_supplied` object
      - `engine_defaults` object
      - `assumptions` string[]
    - `boq_rows` BoQRow[], required
      - `work_section` string, required
      - `description` string, required
      - `quantity` number, required
      - `unit` string, required
      - `rate` number, nullable
      - `amount` number, nullable
      - `attribution` string, required
      - `audit_id` string, nullable
    - `totals_summary` TotalsSummary, required: Schema § 1.4 — totals_summary as a typed wrapper around the free-form dict. Wire shape is a flat object (`{"concrete_m3": {...}, "rebar_t": {...}}`). We model it as `RootModel`-equivalent via `model_config(extra="allow")` + a single `__root__`-style `entries` dict. Pydantic v2 recommends `RootModel` for free-form roots; using a wrapper class keeps the import surface uniform with the rest of this module.
      - `entries` object
    - `text_summary` string, required
    - `drawing` DrawingPreview: Schema § 1.6a — inline server-rendered SVG preview of the geometry. The MCP mirror of the web ``/compute`` response's ``drawing`` field (``api.schemas.compute.DrawingRef``): the same on-screen dimensioned drawing both tiers see in the workspace, surfaced on the MCP compute response so a chat client can render it inline (60th-fire item C). This is the on-screen preview — present on BOTH tiers per the free/paid boundary; the downloadable CAD artefacts (DXF / dimensioned PDF) remain the paid differentiator via the export tools. Defined here rather than importing ``DrawingRef`` because the web ``schemas`` package imports back into this module (``schemas.render``), so reusing it would create an import cycle — the MCP wire contract keeps its own copy, the same pattern as ``BoQRow`` / ``DrawingReference``.
      - `svg_b64` string, required
      - `viewbox_x` number, required
      - `viewbox_y` number, required
      - `viewbox_width` number, required
      - `viewbox_height` number, required
      - `entity_count` integer, required
      - `unit_of_drawing` 'm'
    - `drawings` DrawingReference[], nullable
      - `drawing_type` 'section' | 'plan' | 'elevation' | 'isometric' | 'detail', required
      - `format` 'DXF' | 'PDF' | 'SVG', required
      - `url` string, required
      - `sheet_size` 'A1' | 'A2' | 'A3' | 'A4', required
      - `scale` string, required
      - `layers` string[], nullable
    - `audit_trail` AuditEntry[], nullable
      - `id` string, required
      - `boq_row_index` integer, required
      - `derivation` string, required
      - `formula` string, nullable
      - `parameters_referenced` string[]
    - `warnings` ComputeWarning[]
      - `code` string, required
      - `severity` 'info' | 'advisory' | 'caution', required
      - `message` string, required
      - `affected_params` string[]
      - `detail` object
  - `options` RenderRequestOptions: Caller-tunable render options per launch spec § 3.4.1 a/b/c.
    - `standards` string[], nullable: Subset of {CESMM4, NRM2, SMM7, MMHW} to render BoQ tabs for. Omit for every standard present in the compute_results.
    - `include_backup_tabs` boolean: Per launch spec § 3.4.1b — emit one Backup_<STD>_<NNN> sheet per BoQ row. Excel skill only.
    - `include_procurement_summary` boolean: Per launch spec § 3.4.1c — emit an aggregated Procurement_Summary sheet alongside the BoQ tabs. Excel skill only.
  - `personalisation` PersonalisationFields: Personalisation injected on the cover sheet per schema § 4.3.
    - `project_name` string, nullable
    - `client` string, nullable
    - `prepared_by` string, nullable
    - `branding_mode` 'unbranded' | 'customer' | 'dual': Paid-tier branding mode per launch spec § 3.4.0. `unbranded` (default) = clean white. `customer` = customer firm only, CQ stripped. `dual` = customer + CQ side-by-side. Free-tier callers have this field ignored; their output is always CQ-branded + locked + watermarked.
    - `co_brand` CoBrandFields: Optional customer dual-branding payload per launch spec § 3.4.0.
      - `firm_name` string, required
      - `logo_url` string, uri, nullable: Accepted for the v1 wire contract; logo embedding is deferred. Validated as an http(s) URL today so a future fetch never sees file:// / ftp:// / SSRF payloads.
      - `brand_color_hex` string, nullable: Accepted for the v1 wire contract; theme swap is deferred to a follow-up.

## Response `200`

Successful Response

- union
  - MCPResponseRenderResult
    - `data` RenderResult: Wire response shape — schema § 4 RenderResult. Per Phase 3.4 A.3: each skill has TWO possible delivery modes that map to ``CIVILQUANTS_ARTEFACT_STORE``: * ``local`` (dev default) — base64-inline in ``xlsx_b64`` / ``docx_b64`` * ``r2`` (production) — pre-signed URL in ``xlsx_url`` / ``docx_url`` The validator enforces "exactly one delivery channel per skill" — a single render call returns exactly one of {b64, url} for its target format, never both. The other skill's fields stay null.
      - `xlsx_b64` string, nullable: Base64-encoded .xlsx workbook bytes (Excel skill, local store mode only).
      - `docx_b64` string, nullable: Base64-encoded .docx document bytes (Word skill, local store mode only).
      - `xlsx_url` string, nullable: Pre-signed R2 GET URL for .xlsx (Excel skill, R2 store mode).
      - `docx_url` string, nullable: Pre-signed R2 GET URL for .docx (Word skill, R2 store mode).
      - `pdf_url` string, nullable: Reserved for a future PDF skill backend.
      - `watermark_state` 'free' | 'none', required: `free` when the free-tier sheet protection + watermark is applied; `none` for paid output.
      - `byte_size` integer, required: Decoded size of whichever deliverable was rendered.
      - `skill` string, required: Echoes the resolved skill slug.
    - `error` ErrorEnvelope: Canonical schema § 0.4 — error payload returned in `MCPResponse.error`.
      - `code` 'RATE_LIMIT_EXCEEDED' | 'TIER_INSUFFICIENT' | 'INVALID_INPUT' | 'SCHEMA_VERSION_MISMATCH' | 'SCHEMA_INVARIANT_VIOLATION' | 'UPSTREAM_TIMEOUT' | 'BACKEND_FAILURE' | 'IP_BLOCKED' | 'RESOURCE_NOT_FOUND' | 'RENDER_FAILURE' | 'OCR_LOW_CONFIDENCE' | 'MANUAL_REVIEW_REQUIRED' | 'ACCEPTANCE_REQUIRED' | 'INVALID_TOKEN', required: Canonical schema § 0.5 catalogue. Each member maps to a documented `recoverable` flag (see `IS_RECOVERABLE`) — clients use the flag to decide retry vs surface.
      - `message` string, required
      - `recoverable` boolean, required
      - `retry_after_seconds` integer, nullable
      - `details` object, nullable
    - `_meta` ResponseMeta, required: Canonical schema § 0.2 — present on every MCPResponse. Required fields: `schema_version`, `request_id`, `issued_at`, `tier`. The free-tier-only fields (`upgrade_hint`, `tos_url`, `warranty_disclaimer`, `tier_upgrade`, `rate_limit_remaining`) are populated by the middleware when `tier == "free"`; absent on paid-tier responses.
      - `schema_version` '1.0.0'
      - `request_id` string, required
      - `issued_at` string, required
      - `tier` 'free' | 'paid', required
      - `document_id` string, nullable
      - `deliverables` DeliverableURLs: Canonical schema § 0.3 — R2 signed URLs (24h TTL).
        - `pdf_url` string, nullable
        - `xlsx_url` string, nullable
        - `dxf_url` string, nullable
        - `docx_url` string, nullable
        - `expires_at` string, required
      - `rate_limit_remaining` integer, nullable
      - `upgrade_hint` string, nullable
      - `tos_url` string, nullable
      - `warranty_disclaimer` string, nullable
      - `scope_boundary` ScopeBoundary: Canonical schema § 0.6 — surfaces what this assembly produces vs not.
        - `produced` string[], required
        - `not_in_scope` string[], required
        - `recommended_companions` string[], nullable
      - `tier_upgrade` TierUpgrade: Canonical schema § 0.7 — present on free-tier responses only.
        - `current_tier` 'free'
        - `available_tiers` TierOption[], required
          - `tier_slug` '7_day_pass' | 'solo' | 'studio' | 'practice', required
          - `display_name` string, required
          - `price_local` number, required
          - `currency` 'GBP' | 'SGD' | 'EUR' | 'USD' | 'CAD' | 'AUD' | 'NZD' | 'MYR', required
          - `billing` 'one_off' | 'monthly' | 'annual', required
          - `unlock_summary` string, required
        - `pricing_url` string
        - `pass_credit` PassCreditOffer: Canonical schema § 0.7 — pass-credit upgrade hook.
          - `amount_local` number, required
          - `currency` string, required
          - `redeemable_until` string, required
          - `applies_to` 'solo_first_month'
      - `hint` string, nullable
      - `output_mode` OutputModeInfo: Surfaces what a trimmed ``output_mode`` withheld from ``data``. A trimmed mode (``summary`` / ``material_schedule`` / ``boq`` / ``boq_with_backup``) withholds already-computed fields to keep the chat payload small. The withheld value reads to a client agent as "capability missing" (the box-culvert "geometry builder isn't firing" scare) rather than "pared for size": ``drawing`` and ``audit_trail`` appear as ``null``, ``boq_rows`` as an empty list ``[]`` (its type forbids ``null``) — both indistinguishable from a genuine absent result. This names the withheld fields and the exact mode that brings each back, so the value is unambiguous. Absent on the default ``full_json`` mode — nothing is withheld, nothing to point at.
        - `mode` 'summary' | 'material_schedule' | 'boq' | 'boq_with_backup', required
        - `omitted_fields` string[], required
        - `retrieve_with` string, required
  - MCPResponseAny
    - `data` unknown
    - `error` ErrorEnvelope: Canonical schema § 0.4 — error payload returned in `MCPResponse.error`.
      - `code` 'RATE_LIMIT_EXCEEDED' | 'TIER_INSUFFICIENT' | 'INVALID_INPUT' | 'SCHEMA_VERSION_MISMATCH' | 'SCHEMA_INVARIANT_VIOLATION' | 'UPSTREAM_TIMEOUT' | 'BACKEND_FAILURE' | 'IP_BLOCKED' | 'RESOURCE_NOT_FOUND' | 'RENDER_FAILURE' | 'OCR_LOW_CONFIDENCE' | 'MANUAL_REVIEW_REQUIRED' | 'ACCEPTANCE_REQUIRED' | 'INVALID_TOKEN', required: Canonical schema § 0.5 catalogue. Each member maps to a documented `recoverable` flag (see `IS_RECOVERABLE`) — clients use the flag to decide retry vs surface.
      - `message` string, required
      - `recoverable` boolean, required
      - `retry_after_seconds` integer, nullable
      - `details` object, nullable
    - `_meta` ResponseMeta, required: Canonical schema § 0.2 — present on every MCPResponse. Required fields: `schema_version`, `request_id`, `issued_at`, `tier`. The free-tier-only fields (`upgrade_hint`, `tos_url`, `warranty_disclaimer`, `tier_upgrade`, `rate_limit_remaining`) are populated by the middleware when `tier == "free"`; absent on paid-tier responses.
      - `schema_version` '1.0.0'
      - `request_id` string, required
      - `issued_at` string, required
      - `tier` 'free' | 'paid', required
      - `document_id` string, nullable
      - `deliverables` DeliverableURLs: Canonical schema § 0.3 — R2 signed URLs (24h TTL).
        - `pdf_url` string, nullable
        - `xlsx_url` string, nullable
        - `dxf_url` string, nullable
        - `docx_url` string, nullable
        - `expires_at` string, required
      - `rate_limit_remaining` integer, nullable
      - `upgrade_hint` string, nullable
      - `tos_url` string, nullable
      - `warranty_disclaimer` string, nullable
      - `scope_boundary` ScopeBoundary: Canonical schema § 0.6 — surfaces what this assembly produces vs not.
        - `produced` string[], required
        - `not_in_scope` string[], required
        - `recommended_companions` string[], nullable
      - `tier_upgrade` TierUpgrade: Canonical schema § 0.7 — present on free-tier responses only.
        - `current_tier` 'free'
        - `available_tiers` TierOption[], required
          - `tier_slug` '7_day_pass' | 'solo' | 'studio' | 'practice', required
          - `display_name` string, required
          - `price_local` number, required
          - `currency` 'GBP' | 'SGD' | 'EUR' | 'USD' | 'CAD' | 'AUD' | 'NZD' | 'MYR', required
          - `billing` 'one_off' | 'monthly' | 'annual', required
          - `unlock_summary` string, required
        - `pricing_url` string
        - `pass_credit` PassCreditOffer: Canonical schema § 0.7 — pass-credit upgrade hook.
          - `amount_local` number, required
          - `currency` string, required
          - `redeemable_until` string, required
          - `applies_to` 'solo_first_month'
      - `hint` string, nullable
      - `output_mode` OutputModeInfo: Surfaces what a trimmed ``output_mode`` withheld from ``data``. A trimmed mode (``summary`` / ``material_schedule`` / ``boq`` / ``boq_with_backup``) withholds already-computed fields to keep the chat payload small. The withheld value reads to a client agent as "capability missing" (the box-culvert "geometry builder isn't firing" scare) rather than "pared for size": ``drawing`` and ``audit_trail`` appear as ``null``, ``boq_rows`` as an empty list ``[]`` (its type forbids ``null``) — both indistinguishable from a genuine absent result. This names the withheld fields and the exact mode that brings each back, so the value is unambiguous. Absent on the default ``full_json`` mode — nothing is withheld, nothing to point at.
        - `mode` 'summary' | 'material_schedule' | 'boq' | 'boq_with_backup', required
        - `omitted_fields` string[], required
        - `retrieve_with` string, required

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/civilquants/apis/civilquants-api.md) · [All operations](https://skmtc.dev/civilquants/apis/civilquants-api/llms.txt) · [OpenAPI document](https://skmtc.dev/civilquants/apis/civilquants-api/revisions/84569a3761e1?raw)
