Catalogue-build API reference
Catalogue builds are the authoritative API for agent-driven protection-device imports. All JSON responses use the v1 API envelope. The server derives the organization from the bearer credential; request bodies cannot select or override it. Builds are also creator-private: another user's API key in the same organization cannot read, revise, or apply the build.
Opening a new PDF or native ETAP import does not depend on how many unfinished imports you have. You can keep earlier drafts while starting another import.
Routes and scopes
| Route | Scope | Purpose | |
|---|---|---|---|
POST /api/v1/catalog-builds | catalog:write | Create a build and 1–3 declared PDF source slots. | |
| `GET /api/v1/catalog-builds?status=active\ | all&limit=N` | catalog:read | List your recent builds with id, status, currentVersion, importMode, curveRequirement, timestamps, and title. |
POST /api/v1/catalog-builds/etap | catalog:write | Create a sealed native ETAP build from manifest and record text with reviewed interpretations, bounded to 2 MiB: curves for an exact private target, or with records and no target, a new private fuse series, or Siemens BLH or QRH2 breaker series, with one SKU per record. | |
POST /api/v1/catalog-builds/etap/inspect | catalog:read | Check a native ETAP manifest and one record, or several records, without creating a build; returns the inspectionFingerprint, rating, trace IDs, pickup and selector evidence, and limitations. No idempotency key. | |
GET /api/v1/catalog-builds/{buildId} | catalog:read | Read current state, issues, review summaries, and applied summary. | |
GET /api/v1/catalog-builds/{buildId}/diagnostics | catalog:read | Read per-graph extraction stages, blockers, recorded attempts, cache status, and code fingerprints, including failures with no curves. | |
POST /api/v1/catalog-builds/{buildId}/sources/{sourceId}/upload-ticket | catalog:write | Issue a new 15-minute capability; unfinished sources rotate generation, while validated exact replays retain it. | |
PUT /api/catalog-build-uploads/{sourceId} | upload capability | Stream the exact declared PDF; no bearer scope is used. | |
POST /api/v1/catalog-builds/{buildId}/process | catalog:write | Return HTTP 202 after accepting processing; compile and auto-apply in the background when clear. | |
PATCH /api/v1/catalog-builds/{buildId} | catalog:write | Apply up to 20 typed resolutions against expectedVersion. | |
DELETE /api/v1/catalog-builds/{buildId} | catalog:write | Cancel editable work and enqueue exact cleanup keys. | |
GET /api/v1/catalog-builds/{buildId}/curves/{curveRef}/source | catalog:read | Return a bounded source-page review image with no-store. Needs only build ownership, not paid access. | |
GET /api/v1/catalog-builds/{buildId}/curves/{curveRef}/overlay | catalog:read | Return a bounded extracted-overlay review image with no-store. Needs only build ownership, not paid access. |
Every bearer-authenticated mutation requires Idempotency-Key. Create and upload-ticket responses store only a capability descriptor and derive the secret token when presented; GET never returns a token. Authentication happens before request-body parsing.
Create request modes
POST /api/v1/catalog-builds accepts these build controls beside intent and sources:
| Field | Values | Default | Rule |
|---|---|---|---|
curveRequirement | optional, required | optional | Required means every curve-owning compiled record needs at least one newly accepted, saveable manufacturer curve. Separate-trip breaker frames are exempt. |
importMode | package, curves_only | package | Curves-only admits reviewed curve evidence for one exact existing private catalogue record. |
targetCatalogId | UUID | none | Required with curves-only mode. Must be a private record of a category import supports, or create returns CATALOG_BUILD_INVALID_REQUEST. |
Curves-only mode also requires curveRequirement: "required". It preserves the target record's factual data, SKUs, compatibility, factual provenance, and saved curves unless a reviewed curve replacement permits replacing that curve. Existing curves do not satisfy the new-curve requirement.
States and reconnect behavior
created accepts uploads. processing and applying may be polled safely. blocked exposes the complete current issue document and accepted resolution types. applied, failed, canceled, and expired are terminal.
A build that hits a blocking issue with no allowed response, and has no other issue that offers one, cannot continue. EKX finishes it as failed, and safeFailure carries that issue's code and message. A package with only unsupported devices, sources without product facts, terminal provider failures, and too many pages for curves end a build this way.
One extraction examines at most 24 pages across all sources, and curves need every page examined. Curves are in scope when curveRequirement is required, when a source's contentHint is curves or both, or when EKX finds a time-current curve page while preparing the pages. A source set over 24 pages with curves in scope fails before extraction and is not charged. The failed build's safeFailure.code is TOLERANCE_SOURCE_COVERAGE_INCOMPLETE; it is not an upload error. Upload only the pages with the curves and settings.
Process returns HTTP 202 Accepted after saving processing ownership. PDF and provider work continues after the response, including if the client disconnects. Poll GET for progress. If allowedActions includes resume_processing, call process with the current expectedVersion and a new idempotency key. A server restart leaves the build resumable after its processing lease expires. Replaying an earlier accepted request returns its saved response; use GET for the latest state.
GET returns the optional user intent while detailed build content is retained. It becomes null when terminal content is purged after 30 days.
GET also returns targetCatalogId, curveRequirement, importMode, and curvesWaived. These are stored build decisions. Clients must not derive them from the intent, source hints, review state, or the presence of a curve worker job.
curveDiagnostics contains safe build-level telemetry: detectedGraphCount, reviewableTraceCount, curveOutcome, curvesWaived, and safeFailureCode. Detected graphs count document graphs found by processing. Reviewable traces count extracted traces that reached source-and-overlay review. Saved curves remain the separate applied-summary curveCount. These fields never contain source text, filenames, curve points, model output, or object keys. While curve work is pending, curveOutcome is processing. Pending work does not report missing curves or offer continue_without_curves. Missing-curve requirements are evaluated after processing settles. Status reads use saved image references; image retrieval and acceptance verify availability.
After a timeout or disconnect:
- Call GET with
catalog:read. - If the intended mutation is already reflected, continue from the returned version.
- Otherwise retry the exact request with its original idempotency key.
- For an unfinished expired upload, rotate the ticket with its current generation. Never reuse the
old capability after rotation.
Concurrent process or revision requests are fenced by currentVersion and an internal mutation generation. A stale version returns a conflict instead of overwriting newer decisions.
Issue and curve review contract
Each issue has a stable key, safe code, field path, message, evidence locators, allowed response types, and blocksApply. Submit only the minimum operations needed to answer current blocking issues. A later version can replace the issue set, so always use the newest issue key and version. If a submitted key is stale, duplicated, or no longer permits that response, EKX returns CATALOG_BUILD_REVISION_CONFLICT; reread the build before retrying with a new idempotency key.
For an issue path ending in .identity, provide_value must contain one object with manufacturer, modelNumber, and category. The issue evidence lists the supported category values.
For an invalid optional configuration fact, submit provide_value with null to omit that fact after review. You can include reason (1–300 characters) to explain a source discrepancy or a review decision. The saved record retains that explanation and the reviewed values with their source pages. Existing values are retained unless review explicitly replaces them; omitted facts are never replaced with guessed trip settings. Large summaries retain up to 200 entries, including curve warnings and an explicit omitted-detail count with the build ID when needed.
A new breaker requires an explicit configuration.tripMode: integrated, separate_trip_unit, or none. A missing value remains a blocking issue and cannot be resolved with null. Supplied configuration fields must match the selected device category; unsupported fields become review issues rather than unchecked catalogue data.
For a curves-only import, a manufacturer drawing may name a family rather than the full catalogue order number. An unambiguous source identity from the same manufacturer can offer associate_curve with source and target evidence. Verify applicability from the drawing and use the issue's Association item reference; this records the association without changing catalogue facts. The current overlay still requires a separate accept_curve. Association issues also offer redigitize so incomplete extraction can be corrected before association or approval. A native ETAP build names its exact target, optional SKUs, and applicability evidence when it is created, or creates its own series and SKUs from one base model (revisions may be mixed, each record from its own manifest), so its curves arrive associated and need only accept_curve or reject_curve. While a native curve awaits that decision, the build does not raise CURVES_REQUIRED; rejecting every curve raises it. A native build without a target uses the ordinary package identity matching, so it can raise POSSIBLE_IDENTITY_MATCH and fact conflicts against an existing series. Records it leaves out are listed in limitations.
A link to a curve hosted elsewhere is not a curve supplied in the PDF. When document processing successfully finds no graphs, optional imports report not_provided; required imports remain blocked for missing curves. Neither case is an extraction failure.
A required missing curve or final extraction failure can offer continue_without_curves. Submit it only against the current issue key. This build-wide waiver omits every new curve from the build, including accepted candidates, and preserves curves already saved on catalogue records. The same waiver is never a curve-worker draft operation. Polling and retries read the persisted waiver and do not recreate a failed-job blocker. Retry temporary failures while retry_processing remains available.
An accept_curve operation requires the current reviewFingerprint. Before acceptance, retrieve both the source and overlay image and show them to the engineer. Missing or stale images keep the curve blocked. Raw point arrays are never accepted in a revision.
Review copies may be resized for display; the stored canonical source remains unchanged. The plugin's get_catalog_curve_review tool returns both images inline once with a reviews array for candidates sharing that graph. Review every listed candidate against that image pair; you do not need to download the same pair again for each segment. Each accepted candidate still requires its own current fingerprint. Submit at most 20 resolution operations per call and read the current build before submitting another batch.
You can review more than 20 curve candidates, but a build can save at most 20 new curves across all its records, or 500 for a native ETAP build. If you accept more, CURVE_SAVE_LIMIT_EXCEEDED blocks saving and offers reject_curve for each accepted curve. Reject enough curves to bring the accepted set within the limit. Curves retained through keep_existing do not count toward it.
Correct graph calibration
When an issue offers correct_calibration, retrieve the current canonical source image and choose two visible tick marks on each axis. Use the outer graph ticks: these four positions also define the corrected plot bounds. Supply page-point coordinates, with the origin at the page's top left, x increasing rightward, and y increasing downward. For an unrotated 300-DPI canonical PNG, convert original image pixels to page points by multiplying by 72 / 300. Do not measure a resized preview. For a rotated source, map the displayed image back to the source page coordinates first. On a scan printed slightly askew, measure each x tick where its grid line meets the bottom of the plot and each y tick where its grid line meets the left side.
Send this operation inside the revision's operations array, using the current issue key, curveRef, and expectedVersion. The positions and values below are illustrative; measure your own source.
{
"type": "correct_calibration",
"issueKey": "<current issue key>",
"curveRef": "<current curve reference>",
"calibration": {
"x_points": [
{ "position": 80, "value": 100, "label": "100" },
{ "position": 500, "value": 100000, "label": "100000" }
],
"y_points": [
{ "position": 100, "value": 10000, "label": "10000" },
{ "position": 700, "value": 0.001, "label": "0.001" }
],
"x_scale": "log10",
"y_scale": "log10",
"supplied_by_user": true
}
}Each axis requires exactly two points. X values must be positive and increase left to right; y values must be positive and decrease top to bottom. Each scale is log10 or linear. The supplied_by_user flag identifies an explicit correction instead of automatic calibration; it does not accept the resulting curve. Units and current basis still need source evidence.
After correction, follow resume_processing until work settles. Curve references and fingerprints can change during the rerun. Fetch the replacement source and overlay and resolve the new issue set before asking for curve acceptance.
Applied curve outcomes
Each new applied record summary includes curveOutcome and limitations. Older retained summaries may omit both fields. Clients must not invent record attribution when they are absent.
curveOutcome is one of not_expected, not_provided, imported, incomplete, skipped_after_failure, or not_imported. It describes what this build imported, not everything currently saved on the catalogue record. Package-level limitations remain in the applied summary for the final agent report; record-level limitations include only that record's constraints.
Safety and data lifecycle
Creating, uploading, processing, revising, reading, or canceling a catalogue build never mutates a project. Only successful package apply changes the organization-private catalogue, and it commits all series, SKUs, compatibility links, provenance, and accepted curves together.
Build activity extends a 30-day expiry. Build-owned source and overlay images are retained through active review even when ordinary intermediate artifacts age out. Terminal source and curve objects are deleted through exact-key cleanup claims or queues; content-addressed objects remain while referenced elsewhere. After 30 terminal days, detailed build content is purged while the terminal status and applied summary remain available as a safe tombstone. Organization offboarding removes all rows and objects under catalog-build/sources/orgs/{organizationId}/ and catalog-build/review/orgs/{organizationId}/.
Error codes
Route errors use the v1 envelope { "error": { "code", "message" } }. Validation errors can add details. Codes are stable; messages can change. A 429 or IDEMPOTENCY_IN_PROGRESS response includes Retry-After.
Authentication, access, and requests
| Code | Status | Meaning |
|---|---|---|
UNAUTHORIZED | 401 | The bearer credential is missing, invalid, expired, or revoked. |
FORBIDDEN | 403 | The credential lacks the route's scope, or cannot write catalogue data. |
RATE_LIMITED | 429 | Too many requests. Wait Retry-After seconds, then retry the same request. |
CATALOG_BUILD_ORGANIZATION_REQUIRED | 403 | The credential is not bound to a workspace. |
NOT_FOUND | 404 | You are not an active owner or administrator of the credential's workspace. |
PAID_PLAN_REQUIRED | 402 | The workspace has no paid plan. Reading your own builds and their review images, and canceling them, still work. |
INVALID_JSON | 400 | The body is not valid JSON. |
REQUEST_TOO_LARGE | 413 | The body exceeds the route limit, such as 2 MiB for native ETAP requests. |
INVALID_CATALOG_BUILD_REQUEST | 400 | The body failed validation; details lists each path. |
CATALOG_BUILD_INVALID_REQUEST | 400 | The request is well formed but not allowed, for example an invalid native ETAP interpretation. |
INVALID_BUILD_ID, INVALID_SOURCE_ID, INVALID_CURVE_REF | 400 | A path identifier is malformed. |
IDEMPOTENCY_KEY_REQUIRED, INVALID_IDEMPOTENCY_KEY | 400 | The mutation needs a valid Idempotency-Key. |
IDEMPOTENCY_CONFLICT | 409 | The key was already used for a different request. |
IDEMPOTENCY_IN_PROGRESS | 409 | The original request with this key is still running. |
APPLICATION_ORIGIN_NOT_CONFIGURED | 503 | The server cannot build upload links. Retry later. |
INTERNAL_ERROR | 500 | An unexpected server error. Read the build before retrying a mutation. |
Builds, processing, and apply
| Code | Meaning |
|---|---|
CATALOG_BUILD_NOT_FOUND | The build does not exist or belongs to another user. |
CATALOG_BUILD_TARGET_NOT_FOUND | The target catalogue record or SKU is not an active private record in this workspace. |
CATALOG_BUILD_SOURCE_NOT_FOUND | The source ID is not part of this build. |
CATALOG_BUILD_NOT_EDITABLE | The build is terminal or no longer accepts this change. |
CATALOG_BUILD_SOURCES_INCOMPLETE | A declared PDF has not finished uploading and validating. |
CATALOG_BUILD_SOURCES_NOT_SEALED | The build's sources are not ready for processing. |
CATALOG_BUILD_PROCESS_CONFLICT | expectedVersion or the build state changed. Read the build. |
CATALOG_BUILD_REVISION_CONFLICT | The issue set changed. Read the build and resolve its current issues. |
CATALOG_BUILD_BLOCKED | Blocking issues remain, so the package cannot apply. |
CATALOG_BUILD_APPLY_CONFLICT | The catalogue changed while applying. Read the build and follow its actions. |
CATALOG_DRIFT | A matched or target catalogue record changed since compilation. |
STALE_WRITE, LEASE_LOST | Another request advanced the build. Read the build before retrying. |
OPERATION_RUNNING | Package extraction is already running. Poll the build. |
CURVE_PROCESSING | Curve work has not settled. Poll until it does. |
CURVE_TOLERANCE_IMPORT_DISABLED | New curve saves are temporarily disabled; a facts-only waiver still works. |
CURVE_EVIDENCE_UNAVAILABLE | Accepted curve evidence could not be read during apply. Retry processing if offered. |
Curve review and revisions
| Code | Meaning |
|---|---|
CURVE_JOB_NOT_FOUND | The build has no curve job for this request. |
CURVE_BUILD_NOT_READY, CURVE_BUILD_NOT_EDITABLE, CURVE_BUILD_STALE | Curve work changed or is not ready. Read the build and use the current review. |
CURVE_HANDOFF_CONFLICT | Curve work was handed off concurrently. Read the build. |
CURVE_SOURCE_UNAVAILABLE | The source PDF for curve work is not available. |
CURVE_RESOLUTION_INVALID | A curve operation does not match the current review, fingerprint, or offered responses. |
CURVE_REVIEW_ARTIFACT_UNAVAILABLE | The source or overlay image is missing or stale; the curve stays blocked. |
TOLERANCE_ASSOCIATION_UNRESOLVED, TOLERANCE_SOURCE_INVALID | The tolerance correction does not match the retained source rules. |
Uploads
PUT /api/catalog-build-uploads/{sourceId} returns these codes, and they can appear as a source's safeErrorCode.
| Code | Meaning |
|---|---|
UPLOAD_UNAUTHORIZED | The Upload capability is missing, malformed, or for another source. |
UPLOAD_EXPIRED | The capability expired. Refresh the upload ticket. |
UPLOAD_STALE | A newer upload generation exists, or the ticket expired after a failed upload. Refresh the upload ticket. |
UPLOAD_CONFLICT | Another upload for this source is in progress or finished with different bytes. |
INVALID_MIME | The request is not Content-Type: application/pdf. |
INVALID_SIZE | The body is empty or its size differs from the declared bytes. |
UPLOAD_TOO_LARGE | The body is larger than the declared bytes. |
UPLOAD_TIMEOUT | No bytes arrived for 60 seconds, or the upload ran past its size-based limit. Retry with the same ticket. |
UPLOAD_ABORTED | The client disconnected mid-upload. Retry with the same ticket. |
UPLOAD_STORAGE_UNAVAILABLE | Storage did not respond in time. Retry with the same ticket. |
UPLOAD_IDENTITY_MISMATCH | The bytes differ from the declared SHA-256. |
PDF_VALIDATION_FAILED | The file is not a readable PDF, for example an HTML download page. |
PDF_PAGE_LIMIT | The PDF has more than 500 pages. |
PDF_VALIDATION_UNAVAILABLE | PDF validation is temporarily unavailable. Retry the same upload with the same ticket. |
UPLOAD_FAILED | The upload could not be completed. Retry with the same bytes. |
A failed upload can be retried with the same ticket while it is unexpired. After it expires, call refresh_catalog_import_upload (or the upload-ticket route) and retry with the new generation.
Native ETAP inspection
POST /api/v1/catalog-builds/etap/inspect returns these with status 400. Messages never quote the export's contents. With records, a failing record reports its code in its own result (ok: false), and the request returns 400 only when the manifest itself cannot be read.
| Code | Meaning |
|---|---|
NATIVE_ETAP_INVALID_JSON | The manifest or record text is not JSON. |
NATIVE_ETAP_SOURCE_MANIFEST | This is the source manifest. Send the collection's points/manifest.json. |
NATIVE_ETAP_UNSUPPORTED_FORMAT | The files are not a supported ETAP point export. |
NATIVE_ETAP_UNSUPPORTED_RECORD_TYPE | The format is recognised, but this record type or model does not import yet. |
NATIVE_ETAP_RECORD_NOT_IN_MANIFEST | recordFile does not name exactly one manifest record. |
NATIVE_ETAP_HASH_MISMATCH | The record does not match its manifest hashes. Send both files unchanged. |
NATIVE_ETAP_IDENTITY_MISMATCH | The manifest entry and record describe different devices or streams. |
NATIVE_ETAP_POINT_COUNT_MISMATCH | Point counts differ between the manifest and record. |
NATIVE_ETAP_INVALID_EXPORT | The layout is unsupported or internally inconsistent. |
Issue codes
Each issue has one of these code values. Resolve it only with the issue's allowed responses.
| Code | Meaning |
|---|---|
MISSING_FACT, AMBIGUOUS_FACT, CONFLICTING_FACT | A required or supplied fact is absent, unclear, or contradicted by the sources. A conflict covering a whole configuration accepts provide_value with an object of corrected fields. A pickup choice that looks like amperes rather than multiples of rated current is raised here; supply corrected values or resubmit them to confirm. |
UNSUPPORTED_UNIT | A value uses a unit EKX cannot convert. |
MULTIPLE_PRODUCTS | Two proposed products share one reference, or two package items resolve to one catalogue record. Answer choose_candidate with the localRef of the item to keep; the others are omitted with a limitation. With targetCatalogId at path targetCatalogId, no item has the target record's category: answer choose_candidate with the localRef of the item that updates the record; the others import as their own records. |
POSSIBLE_IDENTITY_MATCH, INACTIVE_MATCH_REQUIRES_RESTORE | An existing record may be the same device, or a matching record or part number is inactive or archived. Answer restore with the ID from the issue evidence to bring it back; EKX never reactivates an archived record or part without that decision. |
SYSTEM_DEFAULT_EXTENSION_UNSUPPORTED | The import would change a system catalogue record. |
SCALAR_CONFLICT, SKU_CONFLICT | A saved value or SKU differs from the new evidence. |
INVALID_CATALOG_NAME | A series, record name, part number or variant breaks the naming rules in the message: a series is the maker's product family as printed, and a part number is a real orderable number. Answer provide_value with a corrected name, or null for a made-up part number to keep the part with only its variant. |
INVALID_COMPATIBILITY | A proposed compatibility link is not valid. Answer keep_existing to drop the link with a limitation. |
UNSUPPORTED_CATEGORY | No item in the package has a supported device category. When a supported item remains, EKX leaves out each unsupported item, such as an overload relay in a breaker datasheet, and records a limitation instead of raising this issue. |
FACT_PACKET_INSUFFICIENT | The sources did not establish enough facts. |
SOURCE_DRIFT, CATALOG_DRIFT | A source or catalogue record changed after extraction. A drift issue with CATALOG_UNIQUE_CONFLICT means the saved catalogue already holds a conflicting part number. |
INSTALLED_PROFILE_IMPACT | A change affects devices installed in projects; acknowledge it before apply. |
CURVE_CALIBRATION_REQUIRED | Graph axes need a calibration correction. |
CURVE_ASSOCIATION_REQUIRED | A curve needs an explicit association to the target record. |
CURVE_APPROVAL_REQUIRED | A curve needs the engineer's approval with accept_curve, or rejection. |
CURVE_SAVE_LIMIT_EXCEEDED | More than 20 new curves (500 for native ETAP) were accepted. |
CURVE_CONFLICT | A new curve conflicts with a saved curve. |
CURVE_REVIEW_ARTIFACT_UNAVAILABLE | Review images are missing for a curve. |
CURVES_REQUIRED | A required build has no accepted new curve for a record. |
CURVE_EXTRACTION_FAILED | Curve extraction could not finish. |
CURVE_GRAPHS_FAILED | Some graphs failed while others reached review. Does not block apply; answer retry_processing to retry only the failed graphs. |
CURVE_TOLERANCE_IMPORT_DISABLED | New curve imports are turned off on this server. continue_without_curves saves the facts when curves are optional. |
AI_CREDITS_EXHAUSTED | The workspace has no AI credits left. Add credits, then answer retry_processing. |
RETRYABLE_PROVIDER_FAILURE, RETRYABLE_WORKER_FAILURE | A temporary extraction failure; retry when retry_processing is offered. |
TERMINAL_PROVIDER_FAILURE, TERMINAL_WORKER_FAILURE | Extraction failed permanently for this build. The evidence names the reason: MODEL_OUTPUT_TRUNCATED, PROVIDER_REQUEST_REJECTED, FACT_PACKET_OVER_BUDGET, or invalid model output. Retrying the same sources cannot succeed; start a new build with fewer or smaller documents. |
TOLERANCE_* | A current or time tolerance is unresolved: TOLERANCE_SOURCE_COVERAGE_INCOMPLETE, TOLERANCE_ASSOCIATION_UNRESOLVED, TOLERANCE_UNSUPPORTED_EXPRESSION, TOLERANCE_REFERENCE_UNKNOWN, TOLERANCE_CONDITION_UNRESOLVED, TOLERANCE_CONFLICT, TOLERANCE_BOUND_UNKNOWN, TOLERANCE_GEOMETRY_RELATION_UNRESOLVED, TOLERANCE_REPRESENTATION_UNSUPPORTED, TOLERANCE_INVALID_BOUNDARY, TOLERANCE_NOT_STATED, TOLERANCE_DEFINITION_INVALID, or TOLERANCE_PROJECTION_MISMATCH. TOLERANCE_SOURCE_COVERAGE_INCOMPLETE with no allowed response means the sources have more than 24 pages in total while curves are in scope; see States and reconnect behavior. |
Failure codes
A failed, canceled, or expired build reports safeFailure.code, and curveDiagnostics can report safeFailureCode. Besides the provider and worker failure codes above, these can appear:
| Code | Meaning |
|---|---|
PACKAGE_EXTRACTION_FAILED | Fact extraction failed. |
PROVIDER_SCHEMA_REJECTED | The extraction result did not match the required structure. |
CURVE_IMPORT_FAILED | Curve import failed for the build. |
CANCELLED | The extraction request was canceled while running. |
CANCELED_BY_USER | The build was canceled. |
BUILD_EXPIRED | The build expired after 30 days without activity. |
MCP tool errors
The EKX MCP tools return the route codes above in structuredContent.error.code, with httpStatus. They add these codes:
| Code | Meaning |
|---|---|
EKX_TIMEOUT | EKX did not answer within 60 seconds. retryable is true. |
EKX_UNAVAILABLE | EKX could not be reached. retryable is true. |
EKX_RESPONSE_UNREADABLE | EKX answered without a readable JSON body. retryable is true. |
INVALID_EKX_RESPONSE | EKX returned a response the tool could not validate. |
EKX_REQUEST_FAILED | EKX returned an error without a code. |
CURVE_REVIEW_NOT_FOUND | The requested curveRef is not a current review in the build. |
For the three retryable codes, the mutation may have completed. Read the build first, and retry with the same idempotency key only if the change is not reflected. When the MCP server itself rate-limits a request, it answers with HTTP 429, Retry-After, and a JSON-RPC error whose data contains code: "RATE_LIMITED" and retryAfterSeconds. A tool that needs a permission the connection lacks asks you to reconnect EKX; reconnecting requests every EKX permission.