Enterprise Data API
Read your CodeDD audit and portfolio data from your own BI tools, warehouses, and reporting pipelines
On this page
- Quick start
- Creating a key
- Scopes
- Settings scope change
- If you lose a key
- Key management endpoints
- Authentication
- Navigating your data
- Discovery endpoints
- PortCo endpoints
- Repository audit endpoints
- Portfolio endpoints
- Portfolio Monitoring endpoints
- Capability-served endpoints
- Coverage boundary
- Response format
- Null means not measured
- Timestamps
- Developer identity masking
- Pagination
- Caching with ETags
- Definitions
- Schema versions
- Selecting a version
- Default version and sunset plan
- What can change in each version
- v1-legacy
- v2
- Moving from v1 to v2
- Corrections to unversioned endpoints
- Errors
- Rate limits
- Building a reporting pipeline
- Recommended practice
- Security and auditability
- Frequently asked
- Related documentation
The Enterprise Data API gives you programmatic, read-only access to everything CodeDD has analysed for your firm — repository audit results, findings, dependencies, architecture, portfolio-level KPIs and, with Portfolio Monitoring, the monitoring, Insights and CVE data — so you can build your own dashboards, feed a warehouse, or attach CodeDD numbers to an investment committee pack.
It is a REST API over HTTPS returning JSON, authenticated with an organization-scoped API key. Every data endpoint is a GET; nothing in this API can change your data. The numbers are the ones the CodeDD app shows: the app, this API, the in-app AI advisor and the MCP Server read them through the same code.
Quick start#
# 1. Store your key outside your shell history
export CODEDD_API_KEY="codedd_ent_..."
# 2. Ask the API what your key can do
curl -H "Authorization: Bearer $CODEDD_API_KEY" \
https://api.codedd.ai/api/v1/enterprise/
# 3. List the portfolio companies the key covers
curl -H "Authorization: Bearer $CODEDD_API_KEY" \
https://api.codedd.ai/api/v1/enterprise/portcos/Creating a key#
Keys are created by an administrator of your legal organization in Account → Enterprise plan → Data API keys.
- Click Create API key.
- Give it the name of the tool that will use it —
Power BI production, nottest— so you know what breaks if you revoke it. - Choose the scopes it needs (see below).
- Choose a lifetime. Every key expires; the maximum is 365 days.
- Copy the key immediately. It is displayed once and cannot be retrieved again.
A key belongs to the organization, not to the person who created it, so an integration keeps working when that person changes role or leaves. Up to 10 active keys per organization.
Scopes#
Grant only what the consuming tool needs.
| Scope | Grants |
|---|---|
| (any valid key) | The API root, the list of portfolio companies and one portfolio company |
read:audits | Discovery (group audits of a company, group audit metadata, the repository list) and every single-repository endpoint |
read:portfolio | Every group-audit roll-up: KPIs, technical debt, benchmark, estate map, executive summary, category breakdown, development, security findings, DORA, AI-Native, financials, repo activity, supply chain, integration assessment, the AI delivery funnel, innovation commits, contributors, the package catalog, licence coverage and requirement evidence; and per company the KPI history, configuration sections, DORA sync state and settings (see Settings scope change) |
read:monitoring | Portfolio Monitoring: the monitoring summary, metric history, Portfolio Insights, CVE coverage, CVE search ("are we affected?"), company alerts and monitoring schedules. Needs the Portfolio Monitoring module on your contract. |
read:audits does not imply read:portfolio. A pipeline that only needs per-repository findings should not be able to read the firm-level roll-ups you would present to an investment committee.
read:monitoring is a new scope, added on 11 November 2026. No existing key has it: to use these endpoints, create a key with it. It can be selected only when your contract includes Portfolio Monitoring (otherwise Account → Organization shows it disabled, with the reason), and creating a key with it is refused otherwise. Every request also checks the module, so a key keeps the scope but is refused (403 portfolio_monitoring_required) if the module lapses.
An organization key never sees per-person state: no CVE inbox (seen or dismissed alerts), no personal watch rules, no saved scorecard selections or saved Issue Compass selections, and no alert e-mail preferences. Goals are your organization's defaults (source: "default"). The monitoring schedule is read as a fund admin reads it, so it includes what the first audit costs.
Settings scope change#
Announcement (11 November 2026): GET /api/v1/enterprise/portcos/{portco_uuid}/settings/ now requires the read:portfolio scope. Until 9 February 2027 (90 days) a key without it is still served, and the response carries:
Deprecation: @1794355200(RFC 9745: the announcement date)Sunset: Tue, 09 Feb 2027 23:59:59 GMT(RFC 8594: the last day the route is served without the scope)Warning: 299 - "This API key lacks the read:portfolio scope, which /portcos/{portco_uuid}/settings/ requires from 2027-02-10 (403 insufficient_scope). Mint a key with read:portfolio."Link: </documentation/enterprise-data-api>; rel="deprecation"
From 10 February 2027 such a key gets 403 insufficient_scope. Every use without the scope during the grace period is logged, so your CodeDD contact can tell you which keys still need it. To prepare, create a key with read:portfolio (or add it on your next rotation) and switch your pipeline to it. The API root lists the route with "scope": "read:portfolio" and "scope_enforced_from": "2027-02-10".
This page previously listed PortCo settings under read:audits; the route in fact checked no scope at all. It also listed the KPI history under "no scope", while the route has always needed read:portfolio.
If you lose a key#
There is no reveal endpoint, by design: a value that can be fetched again can be fetched by the wrong person. Rotate the key instead. Rotation issues a replacement immediately and keeps the old key working for 24 hours, so you can roll the secret forward on your next deploy rather than at the same instant. The replacement keeps the old key's scopes.
Key management endpoints#
The app's key dialog uses these endpoints. They take a signed-in CodeDD session of an organization administrator, never an API key: a credential must not be able to mint its own successor.
| Endpoint | Method | Does |
|---|---|---|
/ | GET | Lists the organization's keys (prefix, name, scopes, expiry, status) and available_scopes. Each scope entry has requires_module (portfolio_monitoring or null) and available (whether a key holding it would be served today). |
/ | POST | Creates a key and returns its value once |
/ | POST | Issues a replacement; the old key keeps working for 24 hours |
/ | POST | Revokes a key at once |
Authentication#
Send the key as a bearer token on every request:
Authorization: Bearer codedd_ent_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxThe key must not appear in a URL, a query string, or a client-side bundle. It reads your firm's entire audit estate; treat it like a database password and store it in your secret manager.
Navigating your data#
You never have to construct an identifier. Start from the key and walk down:
GET /api/v1/enterprise/ → what this key can do
GET /api/v1/enterprise/portcos/ → your portfolio companies
GET /api/v1/enterprise/portcos/{portco_uuid}/group-audits/ → audits run for one company
GET /api/v1/enterprise/group-audits/{group_audit_uuid}/audits/ → repositories in one audit
GET /api/v1/enterprise/audits/{audit_uuid}/scores/ → results for one repositoryIn the tables below, Tier is the rate-limit bucket (standard or heavy), Paged says whether the endpoint returns meta.next_cursor (Pagination), and ETag whether a finished result carries a validator (Caching with ETags).
Discovery endpoints#
| Endpoint | Scope | Returns | Tier | Paged | ETag |
|---|---|---|---|---|---|
/ | any key | The key's name, prefix, scopes and expiry, the rate limits, and the index of every endpoint with its scope | standard | no | no |
/ | any key | The portfolio companies the key covers | standard | yes | no |
/ | any key | One portfolio company | standard | no | no |
/ | read:audits | The group audits run for one company | standard | yes | no |
/ | read:audits | Group audit metadata and status | standard | no | no |
/ | read:audits | The repository audits of one group audit, with the fields of / | standard | yes | no |
PortCo endpoints#
| Endpoint | Scope | Returns | Tier | Paged | ETag |
|---|---|---|---|---|---|
/ | read:portfolio (enforced from 10 February 2027) | Financial and quality assumptions: hourly_rate_usd, quality_threshold, target_coverage, min_domain_loc_threshold, min_bus_factor, workload_threshold_per_dev, hourly_rate_source. Additive: maturity_stage, mask_developer_identities (true when the setting cannot be read: fail closed), sources (company or default per setting, as the app's settings dialog shows), currency (the currency the company's money figures are in: USD unless the company chose another currency or region; the key hourly_rate_usd keeps its name) and financial_inputs (the saved Financials inputs; null = not set) | standard | no | no |
/ | read:portfolio | Executive KPI values across group audits (?group_audit_uuids=uuid1,uuid2, at most 25; default: the 25 most recent). An entry that could not be computed has every KPI null with available: false and a reason (audit_in_progress, audit_failed, not_in_org, no_repositories (also a group audit that ended with nothing to analyse), error); computed entries have available: true, reason: null. group_audits maps each requested uuid to {status, is_completed, state} with state completed, running, failed, empty (ended with nothing to analyse, see Group audit endings) or removed; a uuid that is not one of the company's group audits is reason: not_in_org with status and state null. A running group audit is never computed from the repositories finished so far. The entries carry values, not a trend: compare two of them only when both group audits covered the same set of repositories (/ lists them; / and / mark a change of set as comparable: false with scope_change) | heavy | no | no |
/ | read:portfolio | One configuration section of the company: settings (values with their sources company / default, defaults, currency, next audit date (the monitoring schedule's when there is one, next_audit_date_source says which; org_next_audit_date is the date set on the company), organization name and status), financial_inputs, comparison_metrics, dora_environment, developer_identity (whether names are masked), package_provenance (the rules, active and revoked), remediation or telemetry. Editor- and initiator-only fields (rule authors, identity baskets, the telemetry connection) are never served to a key. An unknown section is 400 invalid_parameter | standard | no | no |
/ | read:portfolio | The company's delivery-telemetry sync state per repository: provider, sync_status, last_sync_at, credential_ok (the credential seen at the last sync, false when no usable credential was seen; credential_ok_at_last_sync and credential_checked_at repeat it with its check time and are null when the repository never synced; credential_current and git_connection give the connection state now), history coverage. Read from stored state (no provider is called; never a token or a provider id). 404 data_not_available when the company has no group audit the key can read | standard | no | no |
Repository audit endpoints#
All require read:audits. Every result endpoint answers 409 audit_in_progress until the repository audit's status is Audit completed, and 409 audit_failed once it ended without results (failed or cancelled); only /audits/{audit_uuid}/, /architecture/ and /history/ (which lists the repository's other, finished audits) are served while it runs. A result that cannot be read right now on a completed audit answers 503 temporarily_unavailable with Retry-After: 15 (retry shortly); it never answers audit_in_progress for a finished audit.
| Endpoint | Returns | Tier | Paged | ETag |
|---|---|---|---|---|
/ | Name, status, mode, file count, synthesis date; lines_of_code and audited_lines (the audited lines, see Definitions), whole_repo_code_lines, audited_coverage_percent; is_completed (the status is Audit completed) and is_failed (the audit ended without results: failed, errored, cancelled, deleted or expired) | standard | no | no |
/ | Quality scores across all assessed categories. A score the pipeline does not compute is null (dependencies_score) | standard | no | yes |
/ | Full narrative summaries — executive, security, code quality, performance, recommendations. 404 data_not_available when they were never generated | standard | no | yes |
/ | Every flag raised, with severity, location (repository-relative file_path), category_id, owasp, kit_type with its type_basis, and the open and original counts and hours (openFlagCounts, openHours, originalHours, totalFlagCounts) | heavy | no | yes |
/ | Packages, licences, and vulnerabilities. Versioned: v1 gives the dependency totals; v2 adds the package catalog (paged with cursor / limit) and the licence counts as the app's Dependencies tab shows them | heavy | v2 | yes |
/ | Detected technologies, components, and relationships (versioned) | standard | no | yes |
/ | Git-derived development and contribution metrics. Versioned: v1 is the development narrative (markdown string); v2 a structured object with git statistics, contributors, test coverage and extension mix (?time_range=month|quarter|year|all, v2 only) | standard | no | yes |
/ | File statistics, extension mix, and test coverage | heavy | no | yes |
/ | Executive dashboard payload (?time_range=month|quarter|year|all) | standard | no | yes |
/ | Cyclomatic complexity and Halstead grade distribution, with statistics_basis (functions, stored_summary, file_averages or null when nothing was analysed) | standard | no | yes |
/ | Tier-matched peer cohort comparison. cohort_status (ok, too_small, no_tier, not_classified), subject_scores (also without a tier) and score_key_map (each cohort key with the repository score it compares and its label). Additive fields: the repository's tier as CodeDD shows it (archetype_tier_effective), its certainty (archetype_tier_certainty: exact or at_least, a lower bound) and method (archetype_tier_basis: requirement_ladder or legacy_centroid), and cohort_tier_basis (legacy_centroid: peers are matched on the previous method's tier, archetype_primary_tier) | standard | no | yes |
/ | Human vs AI authorship attribution | standard | no | yes |
/ | Own machine-learning code vs AI-integration code (rubric mlr1): lines per role and the top files. Capability-served | standard | no | no |
/ | The other finished audits of the same repository in your organization, with their scores and synthesis dates. Unfinished audits are never listed (unfinished_excluded counts them). Capability-served | heavy | no | no |
ml-roles and history are wrapped as data = {audit: {...header}, sections: {<section>: {available, data | reason}}}.
Unlike the in-app AI advisor, these return complete payloads — no truncated narratives, no "top 5 findings only". The versioned resources are the exception to "payload as the dashboard sees it": they have a stable, versioned shape of their own (see Schema versions).
Portfolio endpoints#
All require read:portfolio and are addressed by group audit; CodeDD derives the portfolio company for you. None sends an ETag.
While a group audit runs, these endpoints answer as the app's dashboard shows it. Before it starts analysing (Opening, Group audit ready) every group resource except the executive summary answers 409 audit_in_progress. While it runs, financials, integration-assessment, estate-map, the KPIs and innovation-commits answer 409 audit_in_progress (the app hides them until completion); benchmark, technical-debt, category-breakdown, development, dora, ai-native, ai-funnel, repo-activity, supply-chain, contributors and security-findings are served from the repositories analysed so far, with meta.partial: true, meta.group_audit_state (in_progress, or failed for a group audit that ended without completing) and meta.note. A completed group audit has meta.partial: false.
| Endpoint | Returns | Tier | Paged |
|---|---|---|---|
/ | One KPI: technical-debt, key-person, innovation, ip-security, scalability. 409 audit_in_progress until the group audit's status is Group audit completed (no partial KPIs from the repositories finished so far); 404 data_not_available when it has no analysable repositories | heavy | no |
/ | Full technical debt dashboard with financial impact (summary.overallDebtScore: see Definitions) | heavy | no |
/ | Benchmark comparison. pulse_overlay lists, for the latest monthly pulse over this group audit, each metric's audit value next to its pulse value (critical_high_cves, innovation_rate_pct, ai_authored_pct, dora_tier; null without a pulse or without Portfolio Monitoring). The category key scalability is labelled "Performance & scalability": it is the repositories' Performance chapter score, not the Scalability KPI | heavy | no |
/ | Estate map of repositories and technologies (versioned) | heavy | no |
/ | Portfolio-level narrative (versioned; v2 adds its provenance) | heavy | no |
/ | Subcategory score breakdown (?category=quality) | heavy | no |
/ | Portfolio development overview | heavy | no |
/ | Issue Compass findings, as the app's explorer lists them (see Security findings) | heavy | yes |
/ | One Issue Compass finding in full (finding_id is a row id from the list; optional ?audit_uuid= and ?source=llm|sonar narrow the match) | heavy | no |
/ | DORA four-key metrics and trends; classification_summary_90 next to the 30-day classification | heavy | no |
/ | The records behind one weekly DORA point: the deploys, changes or incidents of the ISO week containing week_start (YYYY-MM-DD). ?metric= is required: deployment_frequency, lead_time, change_failure_rate, deployment_rework_rate or time_to_restore. Capability-served | standard | no |
/ | AI-Native portfolio assessment | heavy | no |
/ | The AI delivery funnel of the AI-Native tab: AI-authored commits → reviewed → merged via PR → shipped to production, the shipping strip, the agents writing code and the unknown share. Optional window_days = 30, 90 (default), 180 or 365; repositories (comma-separated names, substring) narrows the per-repository rows. Only verifiable AI authorship counts, so the figures are a floor. Capability-served | heavy | no |
/ | The commits behind the innovation-rate KPI, as JSON (the CSV export stays an in-app download). Filters: classification (new_feature, bug_fix, refactoring, maintenance, unplanned), date_from / date_to (YYYY-MM-DD), repositories, window (recent = the KPI's last two years, default, or lifetime); limit 1-100 (default 50). 409 audit_in_progress while the group audit runs. Capability-served | heavy | yes |
/ | Contributor search: commits, domains, activity, repositories, innovation share. q = words of the display name as the app shows it (with masking on, the alias such as dev 3); limit 1-25 (default 10). report.developer_identities_masked says whether names are aliases. Never an e-mail address. Capability-served | heavy | no |
/ | Dollar impact modeling (?key_person_scope=audited|material|root, default audited) | heavy | no |
/ | Repository activity compass | heavy | no |
/ | Package-level licence and vulnerability drilldown (the frozen pre-provenance licence view; for package rows with display names, advisory facts and current licence categories use /) | heavy | no |
/ | The PortCo-wide package catalog: one row per (ecosystem, name) with the repositories[] that use it, licence and licence category, vulnerabilities[] {id, severity, severity_basis (advisory, feed, audit or null), rated, withdrawn, fixed_in, kev, epss} and severity_unattributed, is_direct (declared scope), imported_in_code and scope_basis (declared, imported or unknown), riskiest first. overview carries the catalog totals (with packages_by_import_basis and packages_by_license_scope), repositories_read / repositories_not_finished / repositories_failed, cve_headline (direct is the scored value, transitive next to it; count_unit and distinct_advisories, see Definitions) and license_coverage. Filters vulnerable_only=true, license_risk (copyleft, strong_copyleft, weak_copyleft, unknown; only the third-party rows the licence totals count), q (part of a name); limit 1-50 (default 25). Finished repositories only; 409 audit_in_progress while the group audit runs. Capability-served | heavy | yes |
/ | Licence coverage of the finished repositories: third-party packages by licence category, identified vs unknown with the unknown reasons, company-owned packages excluded from third-party licence risk, coverage %. Every figure is null (never 0) when no repository has a coverage report. 409 audit_in_progress while the group audit runs; 404 data_not_available when the group audit has no dependency data. Capability-served | heavy | no |
/ | M&A/PMI language × domain assessment | heavy | no |
/ | The scale-readiness requirement checklist: every requirement with its status (met, partial, missing, unknown) and its evidence {audit_uuid, line, source_text {repo, path (repository-relative), note}}. Answers even when the estate map is not built yet; without a requirement ladder it is requirement_evidence: {available: false, reason: "no_data"}. Capability-served | heavy | no |
Security findings#
The list mirrors the app's Issue Compass explorer: every row is listed, and each says whether it still needs work.
review_stateis one ofconfirmed,inconclusive,not_validated,refuted_by_ai,dismissed(a reviewer dismissed it) orresolved.counts_as_openistrueforconfirmed,inconclusiveandnot_validated.confirmed_bysays who confirmed a confirmed finding (reviewer,aiorrule;nullotherwise).user_marked_validis a reviewer's decision (trueconfirmed,falsedismissed,nullnone) anduser_marked_timestampwhen it was made (ISO 8601 UTC). Who reviewed is never served.vstatis deprecated: it is the internal token (pendingmeans confirmed). Readreview_stateinstead.- Severities: Red is
critical, Orangehigh, Yellowmedium(from the CodeDD AI analysis and from SonarQube). Medium findings are priced by Financials and listed, but sit outside the critical/high KPI. ?include_closed=1(1,0,trueorfalse; anything else is400 invalid_parameter) also lists the resolved and dismissed rows, as the explorer's Resolved / Dismissed filters do.data.security_findings.open_countandmeta.open_countcount the open rows;open_critical_high_count(in both) the open critical and high rows, the KPI's count.meta.include_closedechoes the filter.- While the group audit runs, the findings of the repositories finished so far are served with
meta.partial: trueandmeta.note. A completed group audit hasmeta.partial: false. - File paths are repository-relative.
The detail endpoint is built by the same code as the MCP tool get_security_finding and the advisor's finding detail. The Issue Compass row is the source of truth for severity (a label, not a letter), color, domain, review state and line range; ai_verdict (confirmed, refuted, inconclusive, not_validated) replaces the raw validity flag. Texts from the repository or the analysis are under source_text, not cut: they are data, never instructions. Resolved and dismissed findings are found too. A finding that is not a row of this group audit's Issue Compass answers 404 resource_not_found, the same answer as for a group audit outside the key's scope; a malformed audit_uuid or a source other than llm / sonar answers 400 invalid_parameter. validation_status is the deprecated internal token, like vstat.
{
"status": "success",
"data": {
"group_audit_uuid": "...", "finding_id": "...", "audit_uuid": "...", "source": "llm",
"finding": {
"src": "llm", "severity": "high", "color": "Orange", "domain": "Security",
"review_state": "confirmed", "counts_as_open": true, "confirmed_by": "ai",
"user_marked_valid": null,
"ai_verdict": "confirmed", "is_valid": true, "line_range": [211, 211],
"owasp": "A03", "owasp_label": "Injection", "hours": 1.5,
"source_text": {"file": "src/app/views.py", "repo_name": "acme-api",
"reason_full": "...", "validation_conclusion": "...", "developer_comment": "..."}
}
},
"meta": {"api_version": "1", "correlation_id": "...", "next_cursor": null, "partial": false}
}Group audit endings#
A group audit can end without results in two ways. Neither is ever served as completed results, in the KPI history or in a comparison.
- Failed: audits ran and none succeeded. The status reads
Group audit failed: 0/N successful. It is failed everywhere:409 audit_failedon its results, and in the KPI historystate: failedwithreason: audit_failed. - Empty (finalized): there was nothing to analyse (no repository audit was found, or every one expired or was deleted). The status reads
Group audit finalized: no repositories analysed (...). It has ended, but it is neither failed nor completed: its KPIs answer404 data_not_available, its KPI history entry hasstate: emptyandreason: no_repositories.
Older group audits whose status reads Group audit completed: 0/N successful, Group audit completed: No audits found or ... expired or deleted are read the same way.
Portfolio Monitoring endpoints#
Scope read:monitoring and the Portfolio Monitoring module. All are capability-served; on per-company routes meta also carries portco_uuid. None is paged unless stated.
| Endpoint | Returns | Tier |
|---|---|---|
/ | The company's Portfolio Monitoring card: the latest assessment, CVEs (critical/ of direct dependencies is the scored value, with transitive counts next to it; scope_basis: imported_in_code, and caveat: no_imported_packages when packages are declared but none is imported; the band then reads unknown, not safe), the bus factor (null with status unknown when no domain is large enough to assess), alerts and signals, the company alerts of Portfolio Admin (company_alerts: setup and Git problems, as /; needs_admin, setup_open, and the counts in health; null when they could not be read), scorecard goals (tracked_metrics, goal_status), audit freshness, the next audit and pulse dates, and the latest pulse. A tracked metric compares the latest audit with the previous one only when both covered the same set of repositories: otherwise it is comparable: false and never worsening (its goal status still reads the current value), and the company row's scope_change says what changed (added, removed, the names under source_text; null for the same set). open_cve_alerts {critical, high} counts the company's open CVE feed alerts next to the scored CVEs; assessment.fix_days_basis names the basis of est_fix_days; the schedule carries latest_run, latest_run_stuck and overdue_run (with overdue_reason). A company the key can read that is not in Portfolio Monitoring answers 404 resource_not_found, as before (the MCP tool and the AI Advisor answer monitored: false with reason: not_monitored instead) | standard |
/ | Twelve months of one scorecard metric: audit samples and monthly pulses with as_of, value and comparable (false marks a basis break, so never compute a trend across it). An audit point is compared only with an audit of the same set of repositories (the repository audits that produced results; a failed one does not count): where a repository was added, removed or swapped, the point is comparable: false and carries scope_change (added, removed, and the names under source_text). metric is required (alias metric_key). critical_high_cves points carry scope: "direct" and transitive_critical_high beside the value. Light and Full audits never share a series: series_audit_mode names the series' mode and omitted_other_mode_audits counts the completed audits of the other mode in the window that are left out | standard |
/ | One Portfolio Insights section for the company, read over your organization (the fund). section is required. pendingCount > 0 means figures are still being computed, so retry in about 15 seconds | heavy |
/ | How complete and fresh CVE screening is: screened repositories, last screening, the CVE headline (scope: "direct", transitive next to it, packages_with_vulns {direct, transitive}), feed (the advisory feed's freshness), count_unit and distinct_advisories (see Definitions), portcos[].flags and portcos[].caveats, packages.distinct_across_repositories, per repository screened_at and earlier_screening_*, and facets.registries[].vulnerable_occurrences. Use ?include=gaps (paged with limit 1-500 and cursor) for unscreened repositories, and ?include=repositories for every repository. Read the flags before concluding "not affected" | heavy |
/ | "Are we affected?" across your companies: one verdict per company (affected, not affected, or unknown because coverage is incomplete; verdicts[].label is the wording) and the affected package uses (groups[].severity_basis says where the severity comes from: feed marks a severity from the OSV feed records). Display-only groups carry aliases_source (advisory or feed) and feed_alias_group (the canonical ID, CVE first, that their OSV feed records share; two groups with one feed_alias_group are one vulnerability). q is an advisory id (CVE-, GHSA-, PYSEC-, GO-, RUSTSEC-, …) or a package, optionally name@version. You can give cve_id, or package with optional version and ecosystem, explicitly instead. Optional: portco (one of your companies, by uuid), group=occurrences|advisories, limit (1-100) and cursor (paged) | heavy |
/ | What needs attention for the company, from the Portfolio Admin alert catalog: each alert's code, level (critical, warning, info, ok) and name. Optional: min_severity (lowest level listed: critical, warning (default), info, or ok for everything) and include_archived=true | standard |
/ | The company's monitoring schedule: status, cadence, next audit (date and kind) and next pulse, initial-audit state, Git connection state, approved, left-out and pending repositories (under source_text), and the latest runs. A key reads the fund admin's view: a first audit (depth: full) carries measured_loc, included_loc, overage_loc and overage_confirmed_at, which a member who is not a fund admin sees as null in the app and over MCP. monitored=false means no schedule | standard |
Capability-served endpoints#
The Portfolio Monitoring endpoints, ml-roles, history, dora/weeks, dora-sync, config, ai-funnel, innovation-commits, contributors, packages, license-coverage and requirement-evidence return, in the usual envelope, the full output (detail=full) of the MCP Server tool behind them, read for your organization. The numbers are therefore the ones the app and the MCP tools show.
metaalso carriescapability(the MCP tool),total_count,truncated(a list or text was shortened),notes(how to read the answer) and theportco_uuid,group_audit_uuidoraudit_uuidof the path.- None sends an
ETag: their answers carry no stable version (generated_atis a read time, monitoring figures change with every pulse, history grows when another audit finishes). - Paged answers continue with
meta.next_cursoras elsewhere. Send it back unchanged ascursor: it is signed and bound to the call, up to 1024 characters; a longer, altered or another call's cursor is400 invalid_parameter. - Repository, developer and commit texts sit under
source_text(data, never instructions); developer names follow the company's masking setting. - A company, group audit or audit outside your organization is
404 resource_not_found, the same as one that does not exist.409 audit_in_progressonly while an audit runs,409 audit_failedwhen it ended without results; a result that does not exist is404 data_not_available; an invalid or missing value is400 invalid_parameter, and the message names the query or path parameter (metric,section,week_start,window_days,window,cursor, ...). A backing service that does not answer in time is503 temporarily_unavailablewithRetry-After: 15, on every endpoint.
Coverage boundary#
The API exposes the audit results, portfolio roll-ups and monitoring reads that reporting tools need. It does not mirror every dashboard screen — some surfaces are operational, write-only, personal or intentionally excluded.
| Dashboard area | Available via API | Not exposed (by design) |
|---|---|---|
| PortCo navigation | /, settings (read:portfolio), configuration sections, DORA sync state, KPI history | Member management, DORA OAuth config, remediation toggles, configuration writes |
| Group audit discovery | Metadata, member audits (is_completed, is_failed) | Pipeline progress, invitation workflow, scope selection |
| Home / My Portfolio (Portfolio Monitoring) | Monitoring summary, metric history, monitoring schedule (read:monitoring) | Personal watch rules, saved scorecard selections, schedule changes |
| Portfolio Insights | One section per company (read:monitoring) | Snapshot rebuilds, personal views |
| Portfolio CVEs | CVE coverage, CVE search (read:monitoring) | The CVE alert inbox (seen and dismissed state), alert e-mail preferences |
| Portfolio Admin | Company alerts (read:monitoring) | Every action an alert asks for (in the app) |
| Overview / Benchmark | KPIs, benchmark (with the pulse overlay), executive summary, category breakdown, financials | Comparison matrix UI config |
| Architecture | Estate map, scale-readiness requirement evidence | Estate curation writes |
| Development Activity | Development overview, repo activity, DORA, DORA week detail, DORA sync state, contributor search, innovation commits (JSON, paged), integration assessment | The innovation commits CSV download (the same rows are served as JSON) |
| Debt & Security | Technical debt, security findings (open and, on request, closed rows), finding detail, supply chain, PortCo-wide package catalog, licence coverage | Saved Issue Compass selections (personal), confirm and dismiss actions |
| AI-Native | AI-Native assessment, AI delivery funnel, per-repo AI authorship, per-repo ML roles | — |
| Single audit — Summary | Scores, summaries, executive, files (aggregates), audit history of the repository | Per-file tree, file search |
| Single audit — Files | Statistics, extension mix, test coverage, complexity | File treetable, file detail drawer, one file's record |
| Remediation | — | Remediation dashboard (module-gated, per user) |
| Admin / ops | — | Audit logs, comparable audits workflow |
The AI delivery funnel and the innovation commits were listed here as not exposed by design; both are now served. The remediation dashboard stays outside the API: it is per user and module-gated.
If you need a dataset that is not listed above, contact your account manager — new endpoints are added when a reporting use case is clear and the underlying data is stable.
Response format#
Every successful response uses the same envelope:
{
"status": "success",
"data": { "portcos": [ { "portco_uuid": "...", "name": "Acme GmbH" } ] },
"meta": {
"api_version": "1",
"correlation_id": "3f2b...",
"next_cursor": null,
"total_count": 12
}
}Quote the correlation_id when contacting support — it identifies the exact request in our logs.
The five versioned resources also state the schema version of data in meta.schema_version.
Null means not measured#
A value CodeDD did not compute is null, never 0 or 100: a score that is not scored, a coverage figure without a domain large enough to assess, a quarter without commits, a ratio without a denominator. A 0 means CodeDD counted and found nothing. An unfinished audit never reads as finished: its results answer 409 audit_in_progress, never values made up from what has been read so far.
Timestamps#
Every timestamp is an ISO-8601 string in UTC ending in Z (2026-11-03T09:15:00Z, with microseconds when the source has them). A date without a time stays YYYY-MM-DD. This applies to the v2 resources and to every capability-served endpoint; the rule is the same one the MCP server applies (fields named *_at, *_date, *_timestamp, *_as_of, timestamp, ai_synthesis, as_of, since or …At). v1-legacy resources and the other unversioned endpoints keep their stored form; where they serve an ISO time (for example user_marked_timestamp on the security findings) it is UTC with a +00:00 offset. Both forms are UTC ISO-8601.
Developer identity masking#
When a portfolio company masks developer identities (its settings: mask_developer_identities), every endpoint serves the stable alias dev N instead of a developer's name or e-mail address. Masking fails closed: an e-mail address with no alias, and any e-mail address inside free text (a finding, evidence, a commit message, a Co-authored-by trailer), is replaced by dev N when it belongs to a known developer and by unknown developer otherwise. With masking off nothing is replaced.
Pagination#
List endpoints are paginated. Pass limit (default 50, maximum 100, unless the endpoint states other bounds) and follow meta.next_cursor until it is null:
import requests
session = requests.Session()
session.headers['Authorization'] = f'Bearer {API_KEY}'
BASE = 'https://api.codedd.ai/api/v1/enterprise'
def fetch_all(path, key):
"""Every page of a list endpoint."""
items, cursor = [], None
while True:
params = {'limit': 100}
if cursor:
params['cursor'] = cursor
payload = session.get(f'{BASE}{path}', params=params, timeout=30).json()
items.extend(payload['data'][key])
cursor = payload['meta']['next_cursor']
if not cursor:
return items
portcos = fetch_all('/portcos/', 'portcos')Treat the cursor as opaque. It is not an offset, and building your own would break the moment ordering changes.
Caching with ETags#
Audit results are written once and then do not change, so repeat reads should be conditional. The repository result endpoints marked ETag above return an ETag; send it back as If-None-Match and a 304 Not Modified costs you nothing:
curl -H "Authorization: Bearer $CODEDD_API_KEY" \
-H 'If-None-Match: "a1b2c3d4..."' \
https://api.codedd.ai/api/v1/enterprise/audits/$AUDIT/scores/A repository audit gets an ETag only once its status is Audit completed. An audit still being analysed, or one that failed, has none, because its data is still moving or will never exist. Each page of a paged versioned resource (dependencies v2) and each time_range of development v2 has its own ETag.
The architecture resource can change after an audit completes: when an admin curates the repository's architecture style, CodeDD stores the repository's architecture model later, or its requirement ladder is recomputed. Its ETag changes with it, so a conditional request then returns the new representation instead of a 304. The tier-benchmark resource's ETag also follows the requirement ladder and the method the tier is shown by. Portfolio, PortCo, discovery and capability-served endpoints send no ETag.
Definitions#
These are the terms whose meaning a pipeline most often gets wrong. The shared Glossary defines every term CodeDD uses, the same for the app, this API, the AI advisor and the MCP server.
| Term | Where | Meaning |
|---|---|---|
| Code health | Overview card; KPI technical-debt | 100 minus the technical-debt KPI's ratio_percent. Bands: Good ≥ 67, Fair ≥ 33, Poor < 33. |
overallDebtScore | / summary.overallDebtScore | The average of the repositories' technical-debt subcategory scores (0-100; unscored repositories left out, null when none is scored). It is not code health; the key keeps its name. |
Fix days, fix_days_basis | KPI technical-debt, / | Three labelled figures. open_red_orange_findings: the KPI's total_estimated_fix_days, the open critical and high findings plus the KPI's other components. issue_compass_scope: Financials technical_debt.total_estimated_fix_days and derivations.capex_remediation_fix_days, over the Issue Compass flag scope (medium included). all_levers_to_targets (effort.total.basis): the Financials headline effort, every lever to its target. flag_scope says which findings were counted. |
| Key-person scope | KPI key-person financial_impact, / ?key_person_scope= | The one-off scenario of losing the top knowledge holder, sized on scope lines: audited (default and headline, the same on the KPI and in Financials), material, or root (the whole repository, served as financial_impact_root). |
Bus factor, domains_at_risk | KPI key-person, monitoring summary | The bus factor counts people. The KPI's count of material domains at risk is domains_at_risk; bus_factor on the KPI is a deprecated alias of it with the same value. |
| AI share | Monitoring summary, metric history, Insights | The share of AI-authored lines in the measurable repositories over the 3 complete months before the audit (ai_authored_pct). The all-history figure is served beside it as ai_authored_pct_all_history. |
Audited lines, loc_basis | /, estate map v2 | audited_lines (and lines_of_code) are the code and documentation lines of the files selected for the audit, the denominator of every per-kLOC figure. whole_repo_code_lines counts the code lines of the whole repository; audited_coverage_percent is audited / whole, one decimal. loc_basis says what a loc figure counts: audited_loc, first_party_loc, total_loc (the whole-repository fallback) or null (no count). |
| Direct vs transitive CVEs | KPI ip-security, /, CVE coverage, monitoring | Critical and high CVEs of direct (shipped) dependencies are the scored value (status_scope / scope: "direct"). Transitive counts are served next to them and never change a status or a goal. A package row's is_direct is the declared scope (the manifest lists it); imported_in_code (the audited code imports it) is the reading the CVE headline calls direct (scope_basis). |
CVE counts, count_unit | / cve_headline, CVE coverage | The headline's counts are package-advisory pairs (count_unit: "package_advisory_pairs"): one advisory two packages carry counts twice. distinct_advisories {direct, transitive} counts each advisory once next to them (null until known). |
Open finding, counts_as_open | Security findings, flags | Confirmed, inconclusive or not validated (review_state). Counts and hours of open findings come from the same rows (openFlagCounts, openHours); totalFlagCounts and originalHours count every flag originally raised. |
Schema versions#
CodeDD's analysis keeps improving. So that an improvement never changes the data your pipeline parses without warning, five resources have a schema version:
| Resource | Endpoint | Default without a version |
|---|---|---|
| Repository architecture | / | v1-legacy until 30 April 2027, then v2 |
| Estate map | / | v1-legacy until 30 April 2027, then v2 |
| Portfolio executive summary | / | v1-legacy until 30 April 2027, then v2 |
| Repository dependencies | / | v1-legacy (no sunset announced; responses carry no Sunset header) |
| Repository development | / | v1-legacy (no sunset announced; responses carry no Sunset header) |
| Version | What it is |
|---|---|
1 (v1-legacy) | The shape these resources had before versions existed, frozen: no field is renamed or removed, and fields CodeDD adds appear only in v2. |
2 | A typed shape with explicit fields and no file-system paths. For the architecture resources: what every relationship means and what evidence it rests on, coverage and data quality, one architecture style with its source, the tier with its status. For dependencies: the package catalog; for development: the structured git object. v2 only grows: new fields may be added, existing ones keep their name and meaning. |
All other endpoints are unversioned and ignore the selector.
Selecting a version#
# Query parameter (or its alias: ?schema_version=v2)
curl -H "Authorization: Bearer $CODEDD_API_KEY" \
"https://api.codedd.ai/api/v1/enterprise/audits/$AUDIT/architecture/?version=2"
# Or a header
curl -H "Authorization: Bearer $CODEDD_API_KEY" -H "X-CodeDD-Schema-Version: 2" \
https://api.codedd.ai/api/v1/enterprise/audits/$AUDIT/architecture/- Accepted values are
1and2(v1,v1-legacyandv2work too). Theversionquery parameter also accepts the aliasschema_version, on every versioned resource. Any other value is a400 invalid_parameter, as are selectors (the two query parameters and the header) that name different versions. - The response states the version it carries: in
meta.schema_versionand in theX-CodeDD-Schema-Versionresponse header (responses vary on that header). A v1 response of an architecture resource served because the request named no version also carries aSunsetheader with the date below: it marks the end of v1 as the default. The dependencies and development resources have no sunset and never send one. A request that namesversion=1gets noSunsetheader, because explicit v1 keeps working after that date. - Each version has its own
ETag, so a conditional request never answers with a304for the other shape. TheETagalso changes when the version's output changes for the same audit (v2 gains a field, a value rule is corrected), so a cached copy is never revalidated across such a change. TheETags of the architecture resource changed once when versions were introduced; expect one full re-read.
Default version and sunset plan#
| Period | Request without a version | version=1 | version=2 |
|---|---|---|---|
| Until 30 April 2027 | v1-legacy | v1-legacy | v2 |
| From 1 May 2027 | v2 | v1-legacy, until its removal is announced in this document | v2 |
This plan covers the three architecture resources. The dependencies and development resources serve v1-legacy to a request that names no version, with no sunset announced: send version=2 to get v2.
Send version=2 now: requests that name a version are unaffected by the default changing.
What can change in each version#
| Version | Fields | Values | Retirement |
|---|---|---|---|
| v1-legacy | Frozen. No field is added, renamed or removed. | A value the analysis now computes correctly is served corrected, under the same field. Every correction is listed in the v1-legacy tables below and in Corrections to unversioned endpoints. | The architecture resources' v1 stops being the default on 1 May 2027, as shown above; dependencies and development keep v1 as their default. Explicit version=1 keeps working until a removal date is announced in this document. Removal is never unannounced. |
| v2 | Grows only. New fields are added and documented in the v2 section. Existing fields keep their name, type and meaning. | Corrected like v1. A field that is null today may carry a value once the analysis records it. | No sunset is planned. A change that would rename, remove or redefine a field gets a new schema version and is never made in v2. |
Both versions change their ETag whenever their output for the same audit changes, whether a field is added or a value rule is corrected. A conditional request therefore never keeps a stale copy. Parse v2 leniently: ignore fields you do not know.
Revisions#
Each version's output has a revision, part of its ETag (schema-<version>.<revision>). A new revision means cached representations revalidate once (expect one full re-read). The current revisions are v1 5 and v2 10.
| Revision | What changed for the same stored data |
|---|---|
| v1 2, v2 2 | Domain recommendations are drawn from evidence (v1 values change); v2 adds each recommendation's trigger |
| v1 3, v2 3 | A domain's aggregate scores follow the code-health rule (v1 values change; its key set is frozen); v2 adds relationships, structured evidence, candidate links, the estate stages and membership, the org consolidation, org domains, deployment platforms, the run outcome, the coverage data source, structural criticality and module dependencies |
| v2 4 | The repository architecture adds model (the provenance of its facts) |
| v2 5 | requirement_ladder_summary on the estate, each estate repository and the repository architecture |
| v1 4, v2 6 | The dependencies and development resources are versioned: their v1-legacy shapes are documented (v1 output unchanged) and v2 serves the package catalog and the structured development object. The dependencies and development responses now carry meta.schema_version, the X-CodeDD-Schema-Version header and Vary: X-CodeDD-Schema-Version |
| v2 7 | The development git documents are fixed shapes (no copied names or free text outside source_text); dependencies licenses is null when the overview could not be computed |
| v2 8 | Every timestamp field is ISO-8601 UTC ending in Z (some were served as stored: a naive time, nanosecond fractions, +00:00, a local offset or epoch numbers). Dependencies rows add package_key, vulnerabilities[] and vulnerabilities_truncated. Development answers 500 internal_error when the test-coverage or extension-mix read fails (it served null, which read as "not measured"). The estate map's org_tier.excluded_repos[] add loc_basis. The security finding detail adds user_marked_valid |
| v2 9 | Money states its real currency: the configuration section documents that its money is in the company's Financials currency (USD when the company chose none); the Financials resource adds a top-level currency, the KPI resource adds currency when the company's costs are not in US dollars, and Insights money rows state each company's currency (mixed on a payload whose companies differ) |
| v1 5 | With masking on, e-mail-only fields (email, developer_email, author_email) are null instead of the dev N label (review 2026-11-11) |
| v2 10 | Review 2026-11-11 corrections: with masking on, e-mail-only fields are null; a dependency vulnerability's cvss is null when no score is known (was 0), a severity the advisory store does not know comes from the OSV feed when it rates the advisory, and development releases are newest first |
v1-legacy#
v1-legacy keeps the fields and nesting these resources had before versions existed. Fields the analysis has gained since appear only in v2. These include coverage and data quality, edge semantics and relationships, structured evidence and candidate links, detection coverage, the shared stack, dimension evidence states, repository kinds, deployment platforms, the estate stages and membership, the org consolidation, structural criticality, module dependencies, provenance and the requirement ladder. The frozen field set reaches into the documents CodeDD's classifiers store: dimension scores, confidence components, tier gap and ceiling analyses, the repository tier breakdown and the group topology summary keep the fields they had.
Only documents without a fixed schema are served as stored, as before, and may carry whatever the analysis wrote: a component's or relationship's properties (the LLM's component document), the insights_* blocks, and the detector blocks coupling_analysis, communication_patterns, flow_patterns, coupling_metrics and communication_flows. Treat their keys as data, not as part of the contract.
The values are not frozen: where the analysis now computes a value correctly, v1 carries the corrected value under the same field.
Repository architecture
| Field | Correction |
|---|---|
archetype_classification.archetype_name, tier_reference.*.name, tier_gap_analysis.next_tier_name | Scale tier names (Foundational, Established, Scalable, High-Scale, Hyperscale). A tier describes operational scale, not an architecture style. |
archetype_classification.architecture_style (and _label), executive_overview.service_architecture | The repository's architecture style decided from evidence, unknown when undecided, instead of a guess from the number of components. |
archetype_classification.primary_tier, archetype_name, confidence, tier_gap_analysis | primary_tier is null when the scale-tier ladder does not apply to the repository (a library, infrastructure code, a command-line tool, ...) or too few of its dimensions have evidence; archetype_name then reads Not applicable or Insufficient evidence and tier_gap_analysis names no next tier. A dimension without evidence no longer counts as the lowest tier, and confidence is how decisively the evidence places the tier, scaled by the share of dimensions with evidence and the completeness of the analysed inputs. |
archetype_classification.dimension_scores.deployment_model, tier_gap_analysis.actions.*.recommendation | The deployment model scores deployment independence and automation (build artefacts, deployment declared as code, automated deploys, environment promotion, zero-downtime and progressive rollouts), not the number of deployable units; platform-managed hosting (PaaS, serverless, ECS) is not scored below Kubernetes. Tier recommendations are fitted to the repository's architecture style, platform and stack and never recommend a capability it already has: no decomposition advice for a single deployable, no Kubernetes advice for a repository on ECS or a PaaS. |
executive_overview.key_metrics.coupling_level | null when the component graph cannot support a coupling figure (edge extraction missing or failed, or fewer than half of the source packages represented). |
executive_overview.key_metrics.primary_communication | Derived from the graph's synchronous and asynchronous edges. |
executive_overview.critical_dependencies, insights_critical_dependencies, key_metrics.critical_dependencies_count | Single points of failure of the component graph (a component that at least 3, and at least 15% of, the graph's components depend on at runtime) instead of keyword matches. The count is null when the graph cannot be searched. |
confidence_basis on edges | corroborated means the source component's files literally reference the target; edges only consistent with other LLM output are mentioned. |
dependency_flow.critical_paths.critical_components, critical_paths.critical_relationships, visualization_data.enhanced_edges[].is_critical | Structural criticality of the component graph, replacing the LLM's high criticality and strength labels. A component is critical when it is an articulation point (removing it cuts at least two components off the rest), has a fan-in at the graph's 90th percentile and at least 3, or is a single point of failure. An edge is critical when it is the only link between part of the graph and the rest (a bridge). Both lists are empty when the graph cannot be assessed (no graph, no relationships). |
visualization_data.enhanced_edges[].visual_properties.stroke_style | Follows the edge's evidence: solid for static evidence (an import, a literal reference, a matched pattern), dashed for an LLM claim consistent with other analysis, dotted for an LLM inference or an edge whose evidence was not recorded. |
metadata.analysis_timestamp | ISO 8601 (UTC) instead of always null. |
metadata.file_analyses_summary.total_files | Counts the stored file analyses (it read 0). |
| Every text value | Paths of CodeDD's analysis machines are removed; file references are repository-relative. |
Estate map
| Field | Correction |
|---|---|
estate_summary.total_cross_repo_edges, estate_summary.coupling_density | Count directed dependency edges between estate repositories only. Shared technology and delivery links (CI, deployment, base images) are not coupling. Edges are matched to repositories by audit, not by name, so the edges of two repositories that share a name are counted. |
edges | New group audits no longer record shared technology (two repositories using PostgreSQL) as an edge; v2 lists it as shared_stack. Directed edges read "source depends on target" (event_dependency edges are no longer served publisher first), undirected ones list their repositories in name order, an edge served in reverse carries no is_critical_path, and evidence that differs only in case or a technology alias is listed once. strength follows confidence (high from 0.85, medium from 0.6); an edge several independent detectors found combines their confidences (detectors that restate the same repository analysis, such as a declared integration and the producer/consumer pairing built from it, count once). A relation curated under an older type name (shared_datastore, shared_library, event_bus) is served under its current type (data_dependency, library_dependency, event_coupling). New group audits no longer draw an API edge between two repositories only because they share a business domain. |
estate_summary.choke_points, estate_summary.blast_radius, org_archetype.topology_summary | Only repositories that others depend on through directed edges, with at least medium evidence confidence. A dependency several detectors found counts once in fan-in, choke points and roles; a shared data store adds no fan-in to either repository. A blast radius lists every repository a failure reaches, directly or through other repositories; a delivery link (base image, CI templates, deployment assembly) reaches only the repositories that build or ship with it, not their callers. A choke point is matched to its repository by audit, so a repository that shares its name with another is not dropped. |
archetype_name, estate_summary.org_archetype_name, org_archetype.org_archetype_name, tier_ceiling_analysis.next_tier_name | Scale tier names. |
archetype_primary_tier, estate_summary.tier_composition, estate_summary.org_primary_tier, org_archetype.org_primary_tier, org_archetype.tier_gap_analysis, org_archetype.tier_ceiling_analysis | A repository without a tier (see the repository architecture) has a null tier and is counted under not_applicable or insufficient_evidence in tier_composition. When no repository of the estate has a tier, the estate has none either: a null org tier and no tier gap or ceiling. |
architecture_style, architectural_style, estate_summary.style_composition | The architecture style decided from evidence, as on the repository architecture. |
domains[].recommendations | Drawn from evidence: a critical-path repository of the domain, a score below the estate average (no longer described as a tier-peer comparison), and the largest next-tier gaps of the domain's repositories with recommendations fitted to each repository; no tier templates. A repository whose tier is shown by the requirement ladder gets no next-tier gap advice (what lifts its tier is the ladder's list of items to build or confirm). v2 adds the trigger each one cites. |
edges (integration edges) | New group audits record an api_dependency or event_coupling edge when a repository's runtime configuration or code references something another estate repository publishes, such as a configured endpoint URL or host, a service name, or a topic or queue. The edge's evidence names the reference, the file it was read from and the identity it matched. A match too weak to draw is not an edge. v2 lists it under relationship_candidates with its reason. |
edges, estate_summary, org_archetype.topology_summary (relationships) | One coupling is counted once. When several detectors or edge types state the same dependency between two repositories over one channel, that dependency counts once in fan-in, choke points, criticality and roles. edges still lists one entry per edge type. v2 groups them as relationships. |
domains, estate_summary.total_systems | List and count only the business domains that at least one repository of this estate map belongs to, plus Unclassified. An organization domain without a repository in this group audit is not a system of this estate. v2 lists every domain of the organization as org_domains. |
is_user_curated_domain, is_user_curated_role, is_user_curated_style, is_user_curated_topology and the curated values | A curation is kept for the repository itself, identified by its organization and git remote, so a re-audited repository keeps its curated domain, role, style, topology role and relations. |
estate_summary.org_primary_tier, org_archetype.org_primary_tier, org_confidence, org_dimension_scores | One consolidation, shared by the group audit, curation and the KPIs. Each repository is weighted by the square root of its lines of code, and in an estate of three or more repositories none carries more than 45% of the weight. Topology roles change the weights only when at least half of the repositories have dependency evidence. A repository a user excluded from the estate tier ("Exclude from benchmark") or one without a tier takes no part. v2 lists those under org_tier.excluded_repos. |
domains[].aggregate_scores | The domain's code health. A repository without audit scores no longer counts as seven zero scores, and a category scored 0 is no longer dropped from composite. composite is the mean of the categories at least one scored repository reports, and each category is the lines-of-code-weighted mean over the scored repositories. The object keeps its fields. v2 adds scored_repo_count. |
Portfolio executive summary: summaries generated with evidence guardrails (v2 provenance.guardrails_applied) make no coupling, role or tier claim the evidence does not support, and state when cross-repository coupling cannot be determined.
Repository dependencies: the unversioned payload as it was served before, frozen: total_dependencies, outdated_count (always 0), vulnerabilities {critical, high, medium, low} and licenses. It keeps the historic licence semantics: licenses is unfiltered (company-owned packages included) and there is no license_counts.
Repository development: the development narrative as a bare markdown string, unchanged.
The value corrections of the unversioned endpoints (developer identity masking, repository-relative paths, 409 for unfinished audits) apply to every version; see Corrections to unversioned endpoints.
v2#
Conventions for every v2 resource:
- Every documented field is always present;
nullmeans unknown, never zero: a count is0only when CodeDD counted nothing, and a ratio without a denominator (dependency_edges_per_repoof an estate without repositories) isnull. Flags such asis_externaland thecuratedflags arefalseunless set. - Every
confidenceis an integer from 0 to 100. - File references are repository-relative paths; no field contains an absolute path.
- Every timestamp is ISO-8601 UTC ending in
Z(see Timestamps). - Stored LLM documents (component
properties, insight blocks, detector blobs) are not served; their facts are, as typed fields. - A list taken from an unbounded source is capped and its total is served beside it (for example
external_systems.total).
Vocabularies
| Field | Values |
|---|---|
relation_kind | directed (source depends on, or acts on, target), undirected (no direction; for example two repositories sharing one data store) |
relation_class | dependency, delivery (build, CI, deployment, base images), commonality (shared technology, never coupling), structural (one component contains another), unclassified |
evidence_tier | deterministic (static evidence in files: imports, manifests, configuration, literal references), curated (asserted by a user of your organization), heuristic (rule-based pairing without a cited file), llm (an LLM's claim), unknown (not recorded) |
tier.tier_status | applicable, not_applicable (the scale-tier ladder does not apply to this kind of repository, e.g. a library or infrastructure code), insufficient_evidence; null when no classification exists |
requirement_ladder_summary.basis | requirement_ladder (the tier is shown by the requirement ladder), legacy_centroid (the tier is shown by the previous method; available is false when no ladder was computed) |
requirement_ladder_summary.certainty | exact, at_least (a lower bound: the next tier could not be verified; never compare it as an exact tier) |
requirement_ladder_summary.areas[].next_state | blocked (the next tier was checked and is not met), not_verified (could not be assessed), beyond_checks (no check exists yet for the next tier), at_top |
repo_kind.source | classifier (classified and stored by the analysis), derived (classified when served, for audits stored before kinds were), none (not determined), display_labels (older audits: a label-only kind) |
coverage.data_source | server (the analysis read a server-side clone), cli_v1 (a CLI submitted its own computed document), cli_v2 (a CLI run driven by the server); null when not recorded |
deployment_platform | container_orchestrated, container_simple (containers on hosts), paas, serverless, vm, unknown (no deployment target found); null when no deployment profile was recorded |
Estate edge types. Every relationship between repositories has one of the types below. A type's meaning does not change. A type can gain an alias, and an edge stored under an alias is served under the type.
type | Meaning | relation_class | relation_kind | channel |
|---|---|---|---|---|
vcs_dependency | Pulls source code from the target (submodule, git dependency) | dependency | directed | library |
library_dependency (alias shared_library) | Depends on a library the target publishes | dependency | directed | library |
build_inheritance | Builds against build configuration the target publishes (parent POM, BOM, composite build) | dependency | directed | build |
catalog_relation | Declares a dependency on the target in its service catalog | dependency | directed | catalog |
contract_dependency | Consumes API contracts or schemas the target owns | dependency | directed | contract |
interface_dependency | Calls an interface of the target named in the architecture analysis | dependency | directed | http |
api_dependency | Calls the target's API (configured endpoint, service name, host) | dependency | directed | http |
api_consumer_producer | API call paired from consumer and producer roles | dependency | directed | http |
event_coupling (alias event_bus) | Consumes a topic or queue the target publishes | dependency | directed | event |
event_dependency | Consumes from the messaging infrastructure the target publishes to; no shared topic was matched | dependency | directed | event |
data_dependency (alias shared_datastore) | Both reference the same data store | dependency | undirected | data |
uses_base_image | Builds on the target's base image | delivery | directed | build |
ci_orchestration | Runs CI/CD workflows or pipeline templates maintained in the target | delivery | directed | build |
deploy_assembly | Assembled and deployed by the target (a platform or GitOps repository) | delivery | directed | deploy |
shared_technology, shared_messaging, shared_database | Both use the same technology, messaging infrastructure or data store name; no shared instance is implied | commonality | undirected | commonality |
unknown | A link whose kind was not recorded | unclassified | undirected | unclassified |
Repository architecture (v2)
| Field | Contents |
|---|---|
schema_version | "2" |
analysis_timestamp, summary | When the analysis ran; its narrative summary |
repo_kind | kind, label, modifiers (e.g. monorepo), confidence, source, reasons |
style | value, label, source (user, deterministic, llm, none), basis, confidence, conflict, conflicting_values, unverified_value (a suggestion while the style is unknown), candidates with their evidence |
tier | available, tier_status, tier_status_reason, primary_tier, name, confidence, mixed_tier_note, dimensions |
metrics | Component, relationship and technology counts, coupling_edges_per_component with coupling_measurable, primary_communication, single_points_of_failure_count with single_points_of_failure_complete |
technologies, patterns | Detected technologies with confidence and evidence; architectural patterns |
components | Graph nodes (first-party and is_external systems): domain, criticality (the LLM's label), confidence, evidence_tier, evidence_basis, key_files (at most 25) and key_file_count, and structural_criticality (below) |
edges | source, target, type, relation_kind, relation_class, communication_type, confidence, evidence_tier, evidence_basis, evidence (at most 10) and evidence_count, and is_critical with its criticality_reason (a critical bridge) |
single_points_of_failure | Components many others depend on at runtime |
coverage | reported, run_status, file and package counts, inventory_capped, external_systems_detected, edge_evidence_tiers (edges per evidence tier), data_source |
data_quality | level (good, partial, low, unknown) and the reasons behind it |
validation | The structural validation of the analysis: status and its checks |
architecture_run | How the analysis run ended: status (completed, partial, failed, unknown), its reasons, and the basis it is known from (run_record, coverage for audits analysed before runs were recorded, none). An unrecorded run is unknown, never a success. |
structural_criticality | What holds the component graph together: assessed (with not_assessed_reason when the graph cannot be searched), complete (false when the graph is partial and the result a lower bound), graph_limitations, node and link counts, deterministic_link_count, critical_node_ids and critical_link_count. available is false for audits analysed before it existed. |
module_dependencies | The deterministic module-level import graph: status (available, single_module, unsupported when no resolver exists for the main language, outdated_resolver, not_analysed, unavailable), primary_ecosystem, resolution_rate, ecosystems, structure (cycles, instability, layer violations), modules_total and edges_total, and the 25 most connected modules and 40 heaviest edges |
model | Where the facts above come from: the typed architecture model of the audit. available, basis (stored by the analysis, or legacy_adapter: rebuilt from the stored results of an audit analysed before the model existed), source, schema_version, algorithm_version with current_algorithm_version and algorithm_current, model_hash (changes only when a fact changes), stored_at and run_status. available is false, with every other field null, when no model could be built. |
requirement_ladder_summary | The repository's tier by the requirement ladder (see "How the tier is determined" below): available, basis, tier_status, tier, certainty, next_tier, catalogue_version (the requirement list, e.g. ladder-v0.2), rule_id (ladder_staged_v2), areas (area, applies, level, next_state per area) and lift (build_count, confirm_count, not_checked_count for the next tier). No requirement results, evidence or file paths. available is false, with every other field null, for an audit analysed before the ladder. |
A component's structural_criticality is {is_critical, reason, reasons, fan_in, fan_out}. Each entry of reasons is a code with its figures: articulation_point (separated_nodes, parts), high_fan_in (fan_in, percentile) or single_point_of_failure (dependent_count). It is null, and so is an edge's is_critical, when the graph could not be assessed or the audit predates structural criticality. Before that, is_critical was the LLM's strength label, and v2 never serves it as structural criticality.
A component of the LLM synthesis has the evidence_tier llm: its boundary, name and responsibilities are the model's, even when the files it cites are real. Its evidence_basis says what backs it: repository_files_verified (the cited files were checked against the analysed repository), repository_files (cited, not checked) or analysis_only (no file cited). A component of the deterministic fallback graph (deterministic_fallback) is deterministic, and an external system (relationship_target) takes the strongest tier of the edges that reference it.
Estate map (v2)
| Field | Contents |
|---|---|
summary | Repository, line and system counts; dependency_edge_count (directed dependencies: the only coupling), dependency_undirected_count, commonality_count, delivery_edge_count, dependency_edges_per_repo, critical dependency counts, role, tier and style composition, choke_points, governance_coverage |
org_tier | The estate's tier: available, tier_status, primary_tier, name, confidence, dimensions, and the consolidation behind it: excluded_repos (each with its reason: user_excluded, not_applicable, insufficient_evidence or classification_unavailable, and its loc with loc_basis: audited_loc, first_party_loc, total_loc or null when it was not recorded), loc_concentration (the repository holding most of the code, with the capped weight it got) and consolidation (model, input_hash). The three are null for estates consolidated before they were recorded. |
repos | One entry per repository: repo_kind, style, tier, estate and topology role, curated flags, governance, scores, evidence_status, dependency_assessed, external_systems (the 25 most confident with total and truncated), deployment_platform with its label, estate_membership (member, in_topology, reason) and requirement_ladder_summary (the repository's ladder tier in the estate, as on the repository resource) |
domains | Business domains with a repository in this estate map: repo_audit_uuids, aggregate_scores (code health; every value null when no repository has audit scores), scored_repo_count, and recommendations (each with the trigger it was drawn from) |
org_domains | Every business domain of your organization, including those with no repository in this estate map (repo_count 0) |
edges | Typed edges between repositories, one per edge type and orientation: relation_kind, direction_meaning, relation_class, channel, evidence_tier, evidence_basis, confidence, evidence_confidence, coupling_weight, detector, is_critical_path, the subject they name (technology, library, topic, service, ...), their evidence and structured evidence_items, and for integration edges the identity_tier, matched_identity and integration_signals. relationship_id names the relationship the edge belongs to, and facet_id the edge itself. |
relationships | One per source, target and channel: the unit that fan-in, choke points and criticality count. It has a stable edge_id, its typed edges as facets (one per type and detector, each with its own confidence and evidence), evidence_confidence, coupling_weight, the strongest evidence_tier, and the merged evidence_items |
relationship_candidates | Pairings too weak to draw, proposed for curation, each with its reason: below_confidence_threshold, loose_identity_match, broker_only (both use one broker, no topic compared), domain_http_pair or domain_single_producer (paired only by shared business domain). They are never relationships and never count as coupling. |
shared_stack | Technology two or more repositories use, with how each repository was found to use it. Commonality, never coupling. |
detection_coverage | What the cross-repository detection could see, per repository and for the estate, including estate.coupling_verdict: no coupling verdict is drawn from missing evidence |
estate_analysis | The estate stages (business_domains, estate_topology, ic_summary): the overall state (done, running, pending, failed, not_recorded), each stage's status, error, attempts and timing, and stale when membership changed after a stage ran |
estate_membership | Which sub-audits take part: included_count (members whose audit completed), topology_count, shown_count, and the excluded sub-audits with their reason. available is false when the membership could not be read. |
requirement_ladder_summary | The company tier by the requirement ladder (rule_id weighted_quantile_v1): the fields of the repository summary, plus input_snapshot_hash (the same hash means the same inputs) and computed_at. org_tier keeps the previous method's values. |
How the tier is determined (requirement ladder). A tier is the highest level whose required capabilities are all evidenced, cumulatively, in every area that applies to the repository. Each requirement is Met, Not met, Not assessed (it could not be checked, for example because it is configured outside the repository, or CodeDD does not check it yet) or Not applicable, and Not assessed never counts against a tier. When the next tier could not be verified, the tier is a lower bound (certainty at_least). The company tier is reached in an area when repositories holding at least 80% of the weight (the square root of their lines of code) reach it there, plus every repository rated critical; the company tier is the lowest area. Libraries, infrastructure code and other repositories without a running scale are not rated. basis says which method the tier is shown by; the tier and org_tier blocks keep the previous method's values until every consumer switches, and v1-legacy does not change.
Two confidences. evidence_confidence (0 to 100) answers whether the relation is real. Detectors that read the same input count once, and independent ones are combined (noisy-OR), so a relation found by the runtime configuration and by the architecture analysis scores above either finding. coupling_weight (0 to 1) answers how significant the relation is architecturally. It is the fixed weight of the edge's type in the edge-type registry, and a relationship takes the largest weight of its types. The weights are: deploy_assembly 1, catalog_relation 0.95, build_inheritance 0.9, interface_dependency 0.88, vcs_dependency and contract_dependency 0.85, ci_orchestration 0.8, api_dependency 0.75, library_dependency, event_coupling and event_dependency 0.7, data_dependency and uses_base_image 0.65, api_consumer_producer (paired from consumer and producer roles) 0.6, unknown 0.5, and shared_technology, shared_messaging and shared_database 0. One is never folded into the other. confidence on an edge equals its evidence_confidence.
Structured evidence. Each item of evidence_items is {repo, file, signal, detector, identity_tier}: the repository whose file holds the evidence, the repository-relative file, the signal that matched (for example BILLING_URL=http://billing-api:8080), the detector that read it, and how precisely the target was identified (coordinate, git, image, chart, runtime_name, host, alias, repo_name, ...). A list holds at most 20 items, and evidence_item_count gives the total.
Portfolio executive summary (v2)
{
"schema_version": "2",
"group_audit_uuid": "8c1e...",
"executive_summary": "## 1. ...",
"provenance": {
"generated_at": "2026-10-04T12:30:00+00:00",
"input_hash": "3f7a...",
"guardrails_applied": true,
"coverage_caveat_applied": true
}
}input_hash is the SHA-256 of the input the summary was generated from: a changed hash means a summary generated from different data. coverage_caveat_applied says whether the summary states that cross-repository coupling cannot be determined from the evidence. Both are null (and guardrails_applied is false) for summaries generated before provenance was recorded. When the provenance cannot be read, every provenance field is null, guardrails_applied included: unknown, not false.
Example: one estate edge in v2 (abridged: the audit and domain identifiers of both ends and the empty subject fields are left out)
{
"source_repo": "checkout",
"target_repo": "payments",
"type": "api_dependency",
"relation_kind": "directed",
"direction_meaning": "depends_on",
"relation_class": "dependency",
"evidence_tier": "deterministic",
"evidence_basis": "static_evidence",
"confidence": 85,
"is_critical_path": true,
"criticality_kind": "structural",
"subject": { "service_name": "payments-api" },
"evidence": ["config/services.yml: PAYMENTS_URL"],
"evidence_count": 1
}Example: the relationship that edge belongs to (abridged). A second detector found the same call in the architecture analysis. Its finding is a separate facet, and because it is independent evidence it raises evidence_confidence.
{
"edge_id": "rel_3c9d0e5a71b24f6a8d10",
"source_repo": "checkout",
"target_repo": "payments",
"channel": "http",
"relation_kind": "directed",
"relation_class": "dependency",
"is_coupling": true,
"types": ["api_dependency", "interface_dependency"],
"evidence_confidence": 93,
"coupling_weight": 0.88,
"evidence_tier": "deterministic",
"is_critical_path": true,
"facets": [
{ "type": "api_dependency", "detector": "integration_matcher", "confidence": 85, "evidence_tier": "deterministic" },
{ "type": "interface_dependency", "detector": "architecture_bridge", "confidence": 55, "evidence_tier": "heuristic" }
],
"facet_count": 2,
"evidence_items": [
{ "repo": "checkout", "file": "config/services.yml", "signal": "PAYMENTS_URL=http://payments-api:8080", "detector": "integration_matcher", "identity_tier": "runtime_name" }
],
"evidence_item_count": 1
}Repository dependencies (v2)
| Field | Meaning |
|---|---|
schema_version | "2" |
audit_uuid | The repository audit |
total_dependencies, vulnerabilities | Totals as in v1 |
licenses | Distinct licences of third-party packages (company-owned packages excluded). null when the overview could not be computed (unknown, not "none"); [] only when there really are none. total_dependencies and vulnerabilities.* are null in that case too |
license_counts | permissive, weak_copyleft, strong_copyleft, unknown, total (declared third-party packages) and company_owned_excluded, as the app's Dependencies tab shows them. null when they could not be computed, never zeros |
package_count | Packages in the catalog (all pages; also meta.total_count) |
packages[] | One page, by name then ecosystem: name, package_key (<registry>/, null without a package identity), ecosystem, version (declared, for display), declared_version, resolved_version, is_direct, is_declared, times_used, vulnerability_count, critical, high, medium, low, vulnerability_ids (CVE/GHSA), vulnerability_ids_truncated, vulnerabilities[] {id, severity (the advisory store's counted severity, or the OSV feed records' when only the feed knows the ID; null when not rated or unknown, never a guess), rated, withdrawn, fixed_in, kev, epss} with vulnerabilities_truncated, vulnerability_kev_ids, vulnerability_max_cvss_score (null when no advisory has a score), scorecard_aggregate_score, licence (null when none was detected), license_category (permissive, weak_copyleft, strong_copyleft, unknown; null for a company-owned package), license_scope (third_party, company_owned, not_declared, runtime), in_license_counts, dependency_provenance, source_text.vulnerability_summary |
Summing license_category over the packages with in_license_counts: true gives license_counts, because both use the same classifier and exclusions. Package names never carry CodeDD's internal registry prefix. The rows equal the rows of the MCP tool list_audit_dependencies at detail=full; two packages of the same name in different ecosystems are two rows. Pages use ?cursor= (from meta.next_cursor) and ?limit= (1-100, default 50). Each page has its own ETag. A finished audit with no packages serves packages: []. A read failure answers 500 internal_error, never an empty catalog.
Repository development (v2)
| Field | Meaning |
|---|---|
schema_version | "2" |
audit_uuid | The repository audit |
git | The git statistics the app's Development tab reads, or null when the repository has none. Fields: time_range; repository {name, url} (the URL never carries credentials); contributor_count; contributors[] ranked by commits: author, name, email, commit_count, first_commit, last_commit, lines_added, lines_removed, files_modified, unique_file_types (null when not recorded, never 0), expertise {total_files, total_files_w_domains, domain_contributions[{domain, files}]} and commit_series ({YYYY-MM-DD: commits}, the activity graph; null when not recorded). The git documents are fixed shapes: time_based_stats {commit_frequency {total_commits, days_active, avg_commits_per_day, avg_commits_per_week, most_active_day {period, commits}, most_active_week}, activity_periods {first_commit_date, last_commit_date, repository_age_days, total_daily_commits}}; code_churn_stats {overall_churn {...}, file_stability {total_files_modified, avg_changes_per_file, files_by_churn_level}, hotspots[] {times_modified, total_changes, total_additions, total_deletions, hotspot_score, domain, source_text.file_path}} (50 at most); collaboration_stats {overview, author_involvement} (counts only); release_stats {total_releases, release_frequency, releases[] {date, changes_count, source_text {tag, message}}} (50 at most); branch_stats {total_branches, active_branches, stale_branches}; merge_stats {total_merges}; meta_info {total_files, first_commit_date, latest_commit_date, source_text.default_branch}. Each is null when not stored, and a date that is not a date is null. Per-branch, per-merge and collaboration-network rows are not served: they carry names the identity mask cannot map |
development_analysis | {source_text: <markdown>}: the v1 narrative. null when it was never generated |
test_coverage, extension_distribution | As in /. null when absent; a failed read answers 500 internal_error instead of null |
developer_identities_masked | true when the company masks developer identities. Contributors (author, name, email) then carry the stable dev N labels, and e-mail addresses in the narrative are masked too |
?time_range= (month, quarter, year, all; default year) selects the activity window. Each value has its own ETag. A finished audit with neither a narrative nor git statistics answers 404 data_not_available; a read failure answers 500 internal_error.
Moving from v1 to v2#
| v1-legacy | v2 |
|---|---|
archetype_classification.primary_tier, archetype_name, confidence | tier.primary_tier, tier.name, tier.confidence (check tier.tier_status first) |
archetype_classification.architecture_style, executive_overview.service_architecture | style.value (with style.source and style.conflict) |
visualization_data.enhanced_nodes | components |
visualization_data.enhanced_edges, component_view.relationships | edges |
executive_overview.key_metrics | metrics |
executive_overview.critical_dependencies | single_points_of_failure |
metadata.coverage, metadata.data_completeness | coverage, data_quality |
dependency_flow.critical_paths, visualization_data.enhanced_edges[].is_critical | structural_criticality, components[].structural_criticality, edges[].is_critical |
estate_summary.total_cross_repo_edges, coupling_density | summary.dependency_edge_count, summary.dependency_edges_per_repo (read detection_coverage.estate.coupling_verdict before drawing a coupling conclusion) |
edges | edges (typed) and relationships (one per source, target and channel, the unit criticality counts) |
domains[].repos[] | repos (and domains[].repo_audit_uuids) |
domains[].aggregate_scores | domains[].aggregate_scores with scored_repo_count |
org_archetype | org_tier (with excluded_repos, loc_concentration and consolidation) |
dependencies licenses (unfiltered) | licenses (third-party only), license_counts and the packages catalog |
| development (markdown string) | development_analysis.source_text, next to git, test_coverage and extension_distribution |
The OpenAPI document of the API describes both versions of each resource (oneOf).
Corrections to unversioned endpoints#
The unversioned endpoints keep their fields: nothing is renamed or removed, and new fields are only added. Their values are corrected where CodeDD computed or served them wrongly, as in v1-legacy. The corrections of November 2026, for every schema version where an endpoint is versioned:
Status and availability
| Endpoint | Field or answer | Before | Now |
|---|---|---|---|
/, / | is_completed | true for every audit with a synthesis date, including running ones (e.g. status File information imported) | true only when the status is Audit completed |
| same | is_failed (new) | — | true when the audit ended without results (failed, errored, cancelled, deleted or expired); such an audit has is_completed: false, so stop waiting for it |
Every repository result endpoint (scores, summaries, flags, dependencies, development, files, executive, complexity, tier-benchmark, ai-authorship) | Answer while the audit runs | 200 with the rows read so far (zero scores, complexity grade A with 0 functions) | 409 audit_in_progress |
| Repository result endpoints | ETag | An in-progress audit had a validator that stayed the same when it finished | A validator only once the audit is completed |
/ | Answer while the group audit runs | 200 with defaults (health 60, debt 40 % critical, innovation 70 % "excellent" from 0 commits) and audit_is_completed: false | 409 audit_in_progress for all five KPIs |
| Repository and group result endpoints | An audit that failed or was cancelled | 409 audit_in_progress, as if it were still running | 409 audit_failed (see Errors); a kpi-history entry of such a group audit has reason: "audit_failed" |
/ | An entry that was not computed | The empty templates (health 100, debt 0 %, IP status safe), or KPIs from the sub-audits finished so far | Every KPI null, available: false, reason; computed entries add available: true, reason: null; new group_audits map |
| KPI, summaries and other group routes | A result that does not exist (no analysable repositories, a narrative never generated, no git statistics) | 409 audit_in_progress | 404 data_not_available (see Errors) |
/ | A running group audit | The rows found so far, without a marker | The same rows with meta.partial: true and meta.note |
/, / | A running group audit | 200 computed from the repositories finished so far | 409 audit_in_progress (the app shows neither until completion) |
/, /, /, /, /, /, /, / | A running group audit | 200 without a marker | The same answer with meta.partial: true, meta.group_audit_state and meta.note; before analysing starts, 409 audit_in_progress |
Group routes, / | A group audit where audits ran and none succeeded (... 0/) | Read as completed | Failed: 409 audit_failed; KPI history state: failed, reason: audit_failed (see Group audit endings) |
Group routes, / | A group audit that ended with nothing to analyse (no audits found, or all expired or deleted) | Read as completed | Ended without results: KPIs 404 data_not_available; KPI history state: empty, reason: no_repositories |
/ (and any repository result endpoint whose read fails) | A read that fails on a completed audit | 409 audit_in_progress | 503 temporarily_unavailable with Retry-After: 15 |
Null instead of a made-up value
| Endpoint | Field | Before | Now |
|---|---|---|---|
/, / | dependencies_score | 0: the pipeline never computes it | null (not scored); the category breakdown no longer lists a dependencies subcategory |
KPI key-person | bus_factor / domains_at_risk, domain_bus_factor_at_risk, domain_bus_factor_total | 0 when no domain is large enough to assess | null |
KPI key-person continuity_summary, group development | active_expertise_coverage_percent, expertiseCoverage | 0 when not measured | null |
KPI innovation | innovation_percent, maintenance_percent, status, trend_*, metrics.* percentages | 50 / 50 / fair placeholders, or 70 % excellent without any git data | null, with data_availability: "unavailable"; on the churn heuristic status: null, data_availability: "fallback_heuristic" and the band in heuristic_band |
KPI innovation | quarterly_effort_mix, a quarter without commits | every percent 0.0 | every percent null (metadata.total_commits stays 0) |
/ | rank and every figure when nothing was analysed; avg_halstead_volume when not stored | rank A, 0 | null |
/ | avg_cyclomatic_complexity, median_complexity, min_complexity, max_complexity, std_dev, complexity_rank | Per-file averages served as function statistics (CLI audits) | Recomputed from the per-function figures when the audit has them (statistics_basis: "functions"); with per-file averages only, median, min, max and standard deviation are null |
/ v2, /, / | vulnerability_max_cvss_score | 0.0 for a package with no scored advisory (the scanner's "no score" default) | null; a real CVSS base score is always above 0. The type (number | null) is unchanged |
/ | release_data.release_velocity.avg_days_between_releases, release_frequency | 0, or 30 days assumed | null when unknown; release_frequency is Unknown without releases |
/ | repository_metrics.total_repository_lines, audit_lines_of_code, audit_coverage_percentage, documentation_lines | 50000 on a read failure, 0 when not found or not measured | null when unknown (documentation_lines is not measured on this path) |
Paths and texts
| Endpoint | Field | Before | Now |
|---|---|---|---|
/, /, security findings, finding detail, the CSV of the in-app export | file_path, function_metrics[].path, file | The path on CodeDD's analysis machine (/) | The repository-relative path (src/) |
/ | Flag texts (reasons_of_flag, validation_*, mitigation_pointer, developer_comment, sonar_message, file_path, …) | Words such as when, has, get, match, update and the characters ; $ { } were stripped (src/ read src/) | Served as stored; only control characters are dropped and whitespace runs collapsed |
KPI ip-security, supply chain | critical_issues[].identifier, component, recommended_fix | The stored name with CodeDD's internal registry prefix ($!pypi$!_cryptography) | The display name (cryptography) |
| Every endpoint, with masking on | Developer names and e-mail addresses | Financials summary.headline, summary.key_person.identifier and people.key_person.identifier carried the real e-mail; an e-mail without an alias passed through everywhere | dev N or unknown developer everywhere (see Developer identity masking); Financials adds summary.key_person.name and people.key_person.name, and its headline says "one person" |
| Every endpoint, with masking on | E-mail-only fields (email, developer_email, author_email) | the dev N label, which read like an address | null (the field stays; the label is in the name fields). Review 2026-11-11 |
Definitions and figures
| Endpoint | Field | Change |
|---|---|---|
KPI key-person | financial_impact | Computed on the audited lines (scope: "audited"), the same figure as Financials (was the whole repository). The whole-repository scenario is added as financial_impact_root |
KPI key-person | exit_exposure, exposure_windows | exit_exposure is now computed (was always null on the API); exposure_windows added (the Exit exposure card's view) |
KPI innovation | dora, data_availability, heuristic_band | Added: the DORA summary the Overview shows, and how the figure was obtained |
KPI ip-security | status_scope, transitive_critical_vulnerabilities_count, transitive_high_vulnerabilities_count | Added; the headline counts and status stay direct (status_scope: "direct") |
KPI key-person | domains_at_risk | Added as the canonical name; bus_factor stays as its deprecated alias |
KPI technical-debt, / | fix_days_basis, flag_scope, derivations.capex_remediation_fix_days_basis, effort.total.basis | Added labels (see Definitions) |
KPI scalability | repo_weights | Added: the per-repository sizes next to repository_details |
/ | summary.overallDebtScore | Documented: the average technical-debt subcategory score, not code health (see Definitions) |
/ | openFlagCounts, openHours, originalHours, category_id, owasp, kit_type, type_basis | Added. Headline counts and hours cover open flags; totalTimeToFixAllFlags covers every flag neither refuted nor dismissed, resolved included, and is not a headline figure |
/ | cohort_status, score_key_map, subject_scores | Added cohort_status and score_key_map; subject_scores is filled also without a tier (was {}) |
/, group audit rows | audited_lines, whole_repo_code_lines, audited_coverage_percent | Added; lines_of_code is unchanged (audited lines) |
/ | maturity_stage, mask_developer_identities, sources, currency, financial_inputs | Added |
/, KPIs technical-debt and key-person, Portfolio Insights | currency | Added: / states its currency at the top (every amount in it, *_usd fields included, is in it); a KPI whose costs are priced at the company's rate states currency when that is not USD; Insights money rows state each company's currency (they all said USD), a payload mixed when its companies differ. A *_usd field outside a block with its own currency is US dollars |
/ | pulse_overlay, category label | pulse_overlay added; the scalability category is labelled "Performance & scalability" (it was "Performance"; the key is unchanged) |
/ | classification_summary_90, summary_90.deployment_frequency.window_days | The 90-day classification is added; the 90-day summary of an estimated portfolio said window_days: 30 |
| Security findings | review_state, counts_as_open, confirmed_by, user_marked_valid, user_marked_timestamp, open_count, open_critical_high_count, ?include_closed= | Added (see Security findings); vstat is deprecated. Medium (Yellow) findings of the CodeDD AI analysis are now listed, as Medium / Not validated, next to SonarQube's: they count in the list and in effort, never in the critical/high KPI |
| Monitoring summary, metric history | AI share | Measurable repositories over the 3 complete months before the audit (was all history over every repository, e.g. 4.1 % now 13.6 %); the old figure is ai_authored_pct_all_history. Until a company's monitoring block is rebuilt, its points carry basis: "all_repositories", window: "all_history" |
| Monitoring summary | Goals | A company without saved objectives is held to CodeDD's default goals (goal_source: "default"), so goal_status, plan_status and the Off-plan count can change; the band (CodeDD's fixed scale) is kept separate from the goal |
/ v2, / | vulnerabilities[].severity, rated, withdrawn, kev, epss | Were null for an ID the advisory store does not know. Now the OSV feed records' rated severity when the feed knows the ID (still null when its record is unrated or withdrawn); withdrawn true or false; kev / epss from the CVEs the feed records list. / adds severity_basis |
/ | license_risk filter | Lists only the third-party rows the licence totals count; it also listed import-only names (standard-library modules), runtime and company-owned packages |
/ | release_data.releases, current_version, last_release_date | The 10 most recent releases, newest first, and the version and date of the newest (were the 10 oldest and the oldest) |
/ v2 | git.release_stats.releases | The 50 most recent, newest first (were the 50 oldest) |
/ | developerActivity[].isBot, summary.totalActiveDevelopers, avgTeamSizePerRepo, monthlyCommits, rollingCommits | isBot was always false, so bots were counted; monthly series ended at the save date or today, and a developer known only from the author statistics had its lifetime commits in one month. Now isBot is true for automation accounts (dependabot, renovate, [bot], ...) and the counts exclude them; monthlyCommits are calendar months ending at the git snapshot (developerActivityWindow) |
KPI key-person | exposure_windows, exit_exposure, change_vs_year_ago_points | Computed as of the group audit's completion date (was the read date), as Portfolio Insights; exposure_windows adds reference_date and anchor (audit, or read_date without a completion date) |
/ | security_flags item labels, summary.headline | Item labels read, for example, "Red (3 open)" (they said "confirmed"): the open findings (confirmed, inconclusive, not validated); the headline says "the N people who committed in the last 2 years (no headcount saved)" unless a headcount is saved (was "your N engineers"; summary.effort.team_size_window_years, team_size_basis); the key person "accounts for X % of the audited-file contributions" (was "owns X % of the code") |
/ | classification_summary, classification_summary_90 | Count the same window as the metrics (ending at metrics_as_of) and the group's repositories (were counted back from now, across the whole organization); adds window_end and window_anchor (end, or now without a stored anchor) |
/ | release_data.releases[].type, type_basis | type came from substrings of the tag (every v1.x release read major). It is now the version part that changed from the version before it (v1.15.1 after v1.15.0 is patch), null for a tag that is not a version number; type_basis (previous_version, version_number, not_semver) is added |
/ | domain_distribution.domains[], not_audited_lines_of_code | The lines of the files the audit did not review were labelled Unclassified; they are now the Not audited row (audited: false; every other row audited: true) and not_audited_lines_of_code is added |
/, repo-activity | languageDomainLoc, languageComplexity, topLanguage | .h headers were a separate C/ language next to C++ (and a C++ repository with many headers had topLanguage: C). Headers now join the repository's C-family language and topLanguage sums every extension of a language |
Security findings, / | owasp of error_handling.swallowed | Was A09 (logging); now A04 (Insecure Design: a mishandled exceptional condition). error_handling.no_log stays A09 |
Value corrections change the ETag of every versioned representation they touch (see revisions).
Errors#
Failures use a stable error_code you can branch on. Messages may be reworded; codes will not change within v1.
| HTTP | error_code | Meaning and what to do |
|---|---|---|
| 400 | invalid_parameter | A parameter failed validation (including an unsupported schema version, a bad cursor, or an unknown metric, section or KPI name). The message names the parameter. Fix the request. |
| 401 | invalid_api_key | Missing, malformed, or unknown key. Check the header. |
| 401 | api_key_expired | Past its expiry. Rotate it. |
| 401 | api_key_revoked | Revoked by an administrator. Ask for a new one. |
| 403 | insufficient_scope | The key lacks the scope this endpoint needs. Mint a key with it. |
| 403 | enterprise_plan_required | No active Enterprise plan on the organization. |
| 403 | portfolio_monitoring_required | The organization's contract does not include Portfolio Monitoring, which every read:monitoring endpoint needs. |
| 404 | resource_not_found | The resource does not exist or is not covered by your key. These are deliberately indistinguishable. |
| 404 | data_not_available | The resource is in scope and nothing is still running, but this result does not exist: no group audit or repositories to read (including a group audit that ended with nothing to analyse, Group audit finalized: ...), a narrative or git statistics that were never produced. Retrying will not help. |
| 409 | audit_in_progress | The audit has not finished, so this result set does not exist yet. Retry later. |
| 409 | audit_failed | The audit ended without results: it failed or was cancelled (is_failed: true on the audit), or it is a group audit where audits ran and none succeeded (Group audit failed: 0/). The audit is not running, so polling will not help; read an earlier completed audit (if CodeDD recovers the audit, for example a group audit whose finalization failed, its status changes). Same status as audit_in_progress: branch on error_code. |
| 429 | rate_limit_exceeded | Honour the Retry-After header. |
| 500 | internal_error | Our fault, including a read that failed (CodeDD answers 500 rather than serve a missing value as null). Retry with backoff; quote the correlation_id. |
| 503 | temporarily_unavailable | A backing service did not answer in time, or a figure is being rebuilt. Honour Retry-After (15 s). This includes a read that failed on a completed audit. |
409 audit_in_progress means "come back later"; 409 audit_failed and 404 data_not_available are final answers for this audit, not errors in your pipeline. A repository or group audit that ended without results (failed or cancelled; is_failed: true on /audits/{audit_uuid}/) answers 409 audit_failed on its result endpoints: stop waiting for it.
{
"status": "error",
"error_code": "insufficient_scope",
"message": "This API key does not have the \"read:portfolio\" scope.",
"correlation_id": "9c1f..."
}Rate limits#
| Bucket | Limit |
|---|---|
| Standard endpoints, per key | 120 requests/minute |
| Heavy endpoints (the heavy tier in the tables above: flags, dependencies, files, audit history, every group-audit roll-up except the DORA week detail, KPI history, Insights, CVE coverage, CVE search), per key | 30 requests/minute |
| All endpoints, per organization | 600 requests/minute |
Limits are per key, not per IP address, so one pipeline cannot throttle another that happens to share an egress address. On a 429, wait for Retry-After seconds; do not retry immediately.
Building a reporting pipeline#
A complete nightly extract:
import os
import requests
API_KEY = os.environ['CODEDD_API_KEY']
BASE = 'https://api.codedd.ai/api/v1/enterprise'
session = requests.Session()
session.headers['Authorization'] = f'Bearer {API_KEY}'
def get(path, **params):
response = session.get(f'{BASE}{path}', params=params, timeout=60)
if response.status_code == 429:
raise RuntimeError(f"Rate limited; retry after {response.headers['Retry-After']}s")
if response.status_code == 409:
return None # Audit still running; nothing to extract yet.
if response.status_code == 404 and response.json().get('error_code') == 'data_not_available':
return None # Finished, but this result does not exist.
response.raise_for_status()
return response.json()['data']
rows = []
for portco in get('/portcos/', limit=100)['portcos']:
group_audits = get(f"/portcos/{portco['portco_uuid']}/group-audits/", limit=100)
for group_audit in group_audits['group_audits']:
group_uuid = group_audit['group_audit_uuid']
debt = get(f'/group-audits/{group_uuid}/kpis/technical-debt/')
if debt is None:
continue
for audit in get(f'/group-audits/{group_uuid}/audits/', limit=100)['audits']:
# Skip repositories whose analysis has not produced results
# (still running, or ended without results: is_failed).
if not audit['is_completed']:
continue
scores = get(f"/audits/{audit['audit_uuid']}/scores/")
rows.append({
'portco': portco['name'],
'group_audit': group_audit['name'],
'repository': audit['name'],
'audited_lines': audit['audited_lines'],
'scores': scores['scores'],
'portfolio_technical_debt': debt['data'],
})
print(f'Extracted {len(rows)} repositories')Recommended practice#
- Store the key in a secret manager, never in source control, a notebook, or a dashboard definition.
- Rotate on a schedule — quarterly is a reasonable default — and always after someone with access leaves.
- Use one key per consuming system. When something misbehaves you can revoke it without taking down every other integration.
- Grant the narrowest scope the tool needs.
- Cache with
ETags and only re-read what changed. Of a completed audit, only the architecture changes later (through curation or a stored architecture model), and itsETagchanges with it. - Retry with exponential backoff on
429and5xx; never retry a4xxother than429. - Check
is_completedbefore extracting results, and treat409 audit_in_progressas "come back later" and404 data_not_availableas "this result does not exist", not as errors. - Store
nullas unknown, never as zero.
Security and auditability#
- Keys are stored only as SHA-256 digests. Neither a database dump nor a query log yields a usable credential.
- Every request is recorded against the key — method, route, resource, status, and outcome — including refusals. Ask support for an access export if you need to evidence who read what.
- Access is re-checked on every request against your live contract (and, for
read:monitoring, the Portfolio Monitoring module), the key's scopes, and the resource's ownership. Nothing is inherited from when the key was created. - Responses are marked
Cache-Control: private, no-storeso audit findings do not linger in an intermediary cache. - All traffic must be HTTPS.
Frequently asked#
Can I write data through this API? No. Every data endpoint is read-only. Audits are started in the app or through the CodeDD CLI.
Can I use it with an MCP client?
Not with an API key. AI assistants such as Claude or ChatGPT connect through the separate MCP Server, where each fund member signs in with their own CodeDD account and sees only what they see in the app. Enterprise API keys (codedd_ent_...) are never accepted by the MCP server, and no MCP tool can list, create, rotate or revoke API keys. The capability-served endpoints of this API return the same data as the MCP tool of the same name.
What happens when our contract lapses?
The API closes within a minute, and existing keys stop working. They resume if the contract is reinstated and has not expired. If only the Portfolio Monitoring module lapses, the read:monitoring endpoints answer 403 portfolio_monitoring_required and every other endpoint keeps working.
Can a key read another firm's data?
No. A key is bound to one legal organization and can only reach portfolio companies, audits, and results that belong to it. Anything else returns 404.
Do invited team members access the API with their login?
No. The data endpoints do not accept session JWTs — only an organization API key (Authorization: Bearer codedd_ent_...). Invited members do not get automatic API access; an organization administrator must create a key and share it with the tool (or person) that needs it. Keys are org-scoped: whoever holds the key can read the firm's entire entitled audit estate, not just the subset one member sees in the web UI.
Who can create or revoke keys? Only administrators of the legal organization (the Enterprise billing entity), via Account → Enterprise plan → Data API keys, while on an active full Enterprise contract.