Hugo Fund Formation Platform16 Apr, 05:05 CET

Clause Export API

Read-only JSON / NDJSON / CSV export of Hugo's clause data — designed for piping into jq, DuckDB, spreadsheets, and other tools.
3
Views
3
Formats
1.0.0
Schema version
5k / 50k
Default / max limit

Endpoints

MethodPathDescription
GET /funds/:fundId/export/clauses Per-fund export. Gated by the existing fund-access middleware.
GET /export/clauses Cross-fund export. Requires at least one fund_id query param. The handler intersects user-supplied fund_id[] with the caller's accessible fund set and silently drops fund IDs the caller cannot see (no 403 — that would leak existence).

Authentication

Same auth as the rest of Hugo. From a browser session the cookie is enough. From a shell, use the CF Access service token already configured for *.nordiclawfirm.com:

source ~/.env.cloudflare
curl -sS \
  -H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
  -H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
  "https://hugo.nordiclawfirm.com/funds/$FUND/export/clauses?view=library&format=ndjson"

Views

Each view returns a different shape and carries a distinct schema discriminator in the schema field of the JSON envelope and in the X-Hugo-Schema header on NDJSON / CSV responses.

view=library · clauses_library_v1

Flat clause list with assignments, lineage, and provenance. Default view.

Fields: id, fund_id, clause_name_id, clause_name, clause_group_id, clause_group_name, text, lifecycle_status, version, is_latest, source, ai_confidence, prev_clause_id, parent_id, change_note, created_at, updated_at, created_by_email, updated_by_email, assignments[], ops_tags[], hugo_url.

view=msl · clauses_msl_v1

Master Side Letter view: clause-centric, with elected_by, eligible_lps, and excluded_lps arrays per clause. Builds on the cached loadMslData() so subsequent calls within 60s are cheap.

Fields: clause_id, clause_name_id, clause_name, clause_group_id, clause_group_name, text, lifecycle_status, source_lp_id, source_lp_name, elected_by[], eligible_lps[], excluded_lps[] (each excluded LP carries an entries[] with the rationale source), sort_order, hugo_url.

view=isl · clauses_isl_v1

Per-LP Individual Side Letter view. Returns clauses owned by the specified LP plus clauses they are MFN-eligible for.

Required: commitment_id (must belong to the fund — validated server-side).

Fields: clause_id, clause_name_id, clause_name, clause_group_id, text, lifecycle_status, version, is_latest, source, ai_confidence, parent_id, prev_clause_id, origin (own | mfn_eligible), source_lp_id, source_lp_name, created_at, updated_at, hugo_url.

Filters

Unknown filter keys are rejected with HTTP 400 carrying {error, code: "invalid_filter", parameter, allowed[]}. There is no silent ignoring — if a filter key is mistyped, the request fails loudly.

Multi-value filters accept either repeated params (?source=manual&source=ai) or comma-separated lists (?source=manual,ai). Both work.

Common (all views)

ParamTypeNotes
viewenumlibrary (default) / msl / isl
formatenumjson (default) / ndjson / csv. Also honored via Accept header.
limitintDefault 5 000, hard cap 50 000. Truncation reported via truncated: true in the envelope and X-Hugo-Truncated header.
fieldscsvProjection — only emit these top-level fields per clause. Default: all.
clause_name_idmultiIN
clause_group_idmultiIN
lifecycle_statusmultiDefault live. Values: draft, live, election, completed, archived, superseded.
sourcemultimanual, upload, ai, bpmsl_upload, msl_upload
is_latestenumtrue (default) / any
qstringFull-text search via clauses_fts
min_confidencefloatgte; rows with NULL ai_confidence are excluded when this filter is set
updated_sinceRFC3339gte
ops_tagmultiFilter by ops tag
investor_category_idmultiFilter by investor category

Library-only

ParamNotes
source_lp_idmulti — filter by originating LP
commitment_idmulti — alternative to source_lp_id
sharedbool — clauses shared across multiple LPs
unassignedbool — clauses with no assignments
bpmslbool — base-paper MSL clauses

Cross-fund-only (/export/clauses)

ParamNotes
fund_idmulti, required (≥1). Capped at 10 (MAX_CROSS_FUND_FUND_IDS). For view=msl, capped at 5 (MSL_CROSS_FUND_FUND_LIMIT). User-supplied IDs are intersected with the caller's accessible fund set; inaccessible IDs are silently dropped.
fund_categorymulti
vintage_fromint year, gte
vintage_toint year, lte

MSL-only

ParamNotes
mfn_stateenum: elected / eligible / excluded
target_lp_idstring — required when mfn_state is set; validated against the fund

ISL-only

ParamNotes
commitment_idrequired; validated against the fund
uniquebool — only this LP's own clauses
electedbool — only clauses elected via MFN
forkedbool — only clauses with a parent_id
low_confidencebool — ai_confidence < 0.6

Response shapes

JSON envelope (default)

{
  "schema": "clauses_library_v1",
  "schema_version": "1.0.0",
  "generated_at": "2026-04-12T21:28:46.037Z",
  "fund": { "id": "fund_northstar8", "name": "Northstar Equity Partners VIII" },
  "filters_applied": { "view": "library", "format": "json", "limit": 3, "lifecycle_status": ["live"] },
  "fields": ["id", "fund_id", "clause_name", "..."],
  "count": 3,
  "truncated": true,
  "clauses": [ /* ... */ ]
}

fund is null for cross-fund responses.

NDJSON

One JSON object per line, no envelope. Metadata moves to response headers so single-line tools like jq -c and DuckDB's read_json_auto('/dev/stdin') can consume it directly.

HeaderValue
Content-Typeapplication/x-ndjson
Cache-Controlprivate, no-store
X-Hugo-Schemae.g. clauses_library_v1
X-Hugo-Schema-Versione.g. 1.0.0
X-Hugo-Countrow count
X-Hugo-Truncatedtrue / false
X-Hugo-Filters-Appliedbase64-encoded JSON of applied filters
NDJSON is currently buffered, not row-by-row streamed. The server builds the full row set in memory then writes it out. True streaming would require restructuring loadMslData / loadMfnData and is deferred.

CSV

Content-Type: text/csv; charset=utf-8. Header row from the active projection (fields= or the view's default). Multi-value cells (e.g. elected_by) are joined with |. RFC 4180 quoting (" escaped as ""). Same X-Hugo-* metadata headers as NDJSON.

Errors

400 responses use the apiBadRequest helper:

{
  "error": "Unknown filter parameter \"bogus\"",
  "code": "invalid_filter",
  "parameter": "bogus",
  "allowed": ["bpmsl", "clause_group_id", "clause_name_id", "..."]
}

Code values: invalid_filter, invalid_value, missing_required, limit_exceeded, not_found (e.g. wrong-fund target_lp_id or commitment_id).

Schema stability

Versioning policy
  • Additive changes are non-breaking under 1.x: new top-level fields, new filter params, new enum values for source / lifecycle_status / etc.
  • Breaking changes bump major: field removals, renames, type changes, meaning changes.
  • Consumers MUST ignore unknown fields and unknown enum values.
  • Field ordering in JSON objects is not guaranteed. Use object keys, not position.

Version is exposed both in the JSON envelope (schema_version) and as the X-Hugo-Schema-Version response header.

Examples

1. Library view, NDJSON, piped through jq

curl -sS "${AUTH[@]}" \
  "$H/funds/$F/export/clauses?view=library&format=ndjson&lifecycle_status=live" \
  | jq -c '{id, clause_name, lp_count: (.assignments|length)}'

2. MSL view as JSON, projected to a few fields

curl -sS "${AUTH[@]}" \
  "$H/funds/$F/export/clauses?view=msl&fields=clause_id,clause_name,elected_by,eligible_lps" \
  | jq '.clauses[] | select((.elected_by | length) > 0)'

3. Per-LP ISL view as CSV

curl -sS "${AUTH[@]}" \
  "$H/funds/$F/export/clauses?view=isl&commitment_id=$COMMITMENT_ID&format=csv&fields=clause_id,clause_name,origin,source_lp_name,text" \
  > clauses.csv

4. Cross-fund library across two funds

curl -sS "${AUTH[@]}" \
  "$H/export/clauses?fund_id=$F1&fund_id=$F2&format=ndjson&lifecycle_status=live"

5. DuckDB ingest

curl -sS "${AUTH[@]}" \
  "$H/funds/$F/export/clauses?view=library&format=ndjson" \
  | duckdb -c "SELECT clause_name, count(*) FROM read_json_auto('/dev/stdin') GROUP BY 1 ORDER BY 2 DESC"

Operational notes

Roadmap

Deferred from v1, considered for v1.1+:

Ctrl+K to open · ↑↓ navigate · Enter go · Esc close
Copied