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
- If you lose a key
- Authentication
- Navigating your data
- PortCo endpoints
- Repository audit endpoints
- Portfolio endpoints
- Coverage boundary
- Response format
- Pagination
- Caching with ETags
- Schema versions of the architecture resources
- Selecting a version
- Default version and sunset plan
- What can change in each version
- v1-legacy
- v2
- Moving from v1 to v2
- 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, and portfolio-level KPIs — 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 endpoint is a GET; nothing in this API can change your data.
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 |
|---|---|
read:audits | Discovery (PortCos, group audits, repository list), PortCo settings, and all single-audit data endpoints |
read:portfolio | All group-audit roll-ups: KPIs, technical debt, benchmark, estate map, executive summary, category breakdown, development, security findings, DORA, AI-Native, financials, repo activity, supply chain, integration assessment, KPI history |
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.
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.
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 repositoryPortCo endpoints#
No scope beyond org membership (any valid key for the firm).
| Endpoint | Returns |
|---|---|
/ | Financial assumptions (hourly_rate_usd, quality_threshold, target_coverage, …) |
/ | Executive KPI values across group audits (?group_audit_uuids=uuid1,uuid2, optional — defaults to the 25 most recent) |
Repository audit endpoints#
All require read:audits.
| Endpoint | Returns |
|---|---|
/ | Name, status, mode, lines of code, file count, synthesis date |
/ | Quality scores across all assessed categories |
/ | Full narrative summaries — executive, security, code quality, performance, recommendations |
/ | Every flag raised, with severity and location |
/ | Packages, licences, and vulnerabilities |
/ | Detected technologies, components, and relationships (versioned, see Schema versions) |
/ | Git-derived development and contribution metrics |
/ | File statistics, extension mix, and test coverage |
/ | Executive dashboard payload (?time_range=month|quarter|year|all) |
/ | Cyclomatic complexity and Halstead grade distribution |
/ | Tier-matched peer cohort comparison. 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) |
/ | Human vs AI authorship attribution |
Unlike the in-app AI advisor, these return complete payloads — no truncated narratives, no "top 5 findings only". The architecture resource is the exception to "payload as the dashboard sees it": it has a stable, versioned shape of its own (see Schema versions).
Portfolio endpoints#
All require read:portfolio and are addressed by group audit; CodeDD derives the portfolio company for you.
| Endpoint | Returns |
|---|---|
/ | One KPI: technical-debt, key-person, innovation, ip-security, scalability |
/ | Full technical debt dashboard with financial impact |
/ | Benchmark comparison |
/ | Estate map of repositories and technologies (versioned) |
/ | Portfolio-level narrative (versioned; v2 adds its provenance) |
/ | Subcategory score breakdown (?category=quality) |
/ | Portfolio development overview |
/ | Issue Compass findings (paginated) |
/ | DORA four-key metrics and trends |
/ | AI-Native portfolio assessment |
/ | Dollar impact modeling (?key_person_scope=audited|material|root) |
/ | Repository activity compass |
/ | Package-level license and vulnerability drilldown |
/ | M&A/PMI language × domain assessment |
Coverage boundary#
The API exposes the audit results and portfolio roll-ups that reporting tools need. It does not mirror every dashboard screen — some surfaces are operational, write-only, or intentionally excluded.
| Dashboard area | Available via API | Not exposed (by design) |
|---|---|---|
| PortCo navigation | /, settings, KPI history | Member management, DORA OAuth config, remediation toggles |
| Group audit discovery | Metadata, member audits | Pipeline progress, invitation workflow, scope selection |
| Overview / Benchmark | KPIs, benchmark, executive summary, category breakdown, financials | Comparison matrix UI config |
| Architecture | Estate map | Estate curation writes |
| Development Activity | Development overview, repo activity, DORA, integration assessment | Innovation commits CSV export |
| Debt & Security | Technical debt, security findings, supply chain | Saved Issue Compass selections, finding detail drawer |
| AI-Native | AI-Native assessment, per-repo AI authorship | AI delivery funnel |
| Single audit — Summary | Scores, summaries, executive, files (aggregates) | Per-file tree, file search |
| Single audit — Files | Statistics, extension mix, test coverage, complexity | File treetable, file detail drawer |
| Remediation | — | Remediation dashboard (module-gated) |
| Admin / ops | — | Audit logs, comparable audits workflow |
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 three versioned resources also state the schema version of data in meta.schema_version.
Pagination#
List endpoints are paginated. Pass limit (default 50, maximum 100) 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. Repository endpoints 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/An audit still being analysed has no ETag, because its data is still moving.
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.
Schema versions of the architecture resources#
CodeDD's architecture analysis keeps improving. So that an improvement never changes the data your pipeline parses without warning, the three architecture resources have a schema version:
| Resource | Endpoint |
|---|---|
| Repository architecture | / |
| Estate map | / |
| Portfolio executive summary | / |
| 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: 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, and no file-system paths. 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
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). Any other value is a400 invalid_parameter, as is a query parameter and a 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 served because the request named no version also carries aSunsetheader with the date below: it marks the end of v1 as the default. 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 |
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. | Stops being the default on 1 May 2027, as shown above. 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.
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. 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.
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.
- 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), 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
}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) |
The OpenAPI document of the API describes both versions of each resource (oneOf).
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). 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. |
| 404 | resource_not_found | The resource does not exist or is not covered by your key. These are deliberately indistinguishable. |
| 409 | audit_in_progress | The audit has not finished, so this result set does not exist yet. Retry later. |
| 429 | rate_limit_exceeded | Honour the Retry-After header. |
| 500 | internal_error | Our fault. Retry with backoff; quote the correlation_id. |
{
"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 (findings, dependencies, files, all portfolio aggregates), 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.
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.
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'],
'lines_of_code': audit['lines_of_code'],
'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", not as an error.
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, 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 endpoint is read-only. Audits are started in the app or through the CodeDD CLI.
Can I use it with an MCP client? The API is the foundation for MCP access to CodeDD data. Contact your account manager about current MCP availability.
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.
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.