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

RouteScopePurpose
POST /api/v1/catalog-buildscatalog:writeCreate a build and 1–3 declared PDF source slots.
`GET /api/v1/catalog-builds?status=active\all&limit=N`catalog:readList your recent builds with id, status, currentVersion, importMode, curveRequirement, timestamps, and title.
POST /api/v1/catalog-builds/etapcatalog:writeCreate 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/inspectcatalog:readCheck 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:readRead current state, issues, review summaries, and applied summary.
GET /api/v1/catalog-builds/{buildId}/diagnosticscatalog:readRead 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-ticketcatalog:writeIssue a new 15-minute capability; unfinished sources rotate generation, while validated exact replays retain it.
PUT /api/catalog-build-uploads/{sourceId}upload capabilityStream the exact declared PDF; no bearer scope is used.
POST /api/v1/catalog-builds/{buildId}/processcatalog:writeReturn HTTP 202 after accepting processing; compile and auto-apply in the background when clear.
PATCH /api/v1/catalog-builds/{buildId}catalog:writeApply up to 20 typed resolutions against expectedVersion.
DELETE /api/v1/catalog-builds/{buildId}catalog:writeCancel editable work and enqueue exact cleanup keys.
GET /api/v1/catalog-builds/{buildId}/curves/{curveRef}/sourcecatalog:readReturn a bounded source-page review image with no-store. Needs only build ownership, not paid access.
GET /api/v1/catalog-builds/{buildId}/curves/{curveRef}/overlaycatalog:readReturn 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:

FieldValuesDefaultRule
curveRequirementoptional, requiredoptionalRequired means every curve-owning compiled record needs at least one newly accepted, saveable manufacturer curve. Separate-trip breaker frames are exempt.
importModepackage, curves_onlypackageCurves-only admits reviewed curve evidence for one exact existing private catalogue record.
targetCatalogIdUUIDnoneRequired 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:

  1. Call GET with catalog:read.
  2. If the intended mutation is already reflected, continue from the returned version.
  3. Otherwise retry the exact request with its original idempotency key.
  4. 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

CodeStatusMeaning
UNAUTHORIZED401The bearer credential is missing, invalid, expired, or revoked.
FORBIDDEN403The credential lacks the route's scope, or cannot write catalogue data.
RATE_LIMITED429Too many requests. Wait Retry-After seconds, then retry the same request.
CATALOG_BUILD_ORGANIZATION_REQUIRED403The credential is not bound to a workspace.
NOT_FOUND404You are not an active owner or administrator of the credential's workspace.
PAID_PLAN_REQUIRED402The workspace has no paid plan. Reading your own builds and their review images, and canceling them, still work.
INVALID_JSON400The body is not valid JSON.
REQUEST_TOO_LARGE413The body exceeds the route limit, such as 2 MiB for native ETAP requests.
INVALID_CATALOG_BUILD_REQUEST400The body failed validation; details lists each path.
CATALOG_BUILD_INVALID_REQUEST400The request is well formed but not allowed, for example an invalid native ETAP interpretation.
INVALID_BUILD_ID, INVALID_SOURCE_ID, INVALID_CURVE_REF400A path identifier is malformed.
IDEMPOTENCY_KEY_REQUIRED, INVALID_IDEMPOTENCY_KEY400The mutation needs a valid Idempotency-Key.
IDEMPOTENCY_CONFLICT409The key was already used for a different request.
IDEMPOTENCY_IN_PROGRESS409The original request with this key is still running.
APPLICATION_ORIGIN_NOT_CONFIGURED503The server cannot build upload links. Retry later.
INTERNAL_ERROR500An unexpected server error. Read the build before retrying a mutation.

Builds, processing, and apply

CodeMeaning
CATALOG_BUILD_NOT_FOUNDThe build does not exist or belongs to another user.
CATALOG_BUILD_TARGET_NOT_FOUNDThe target catalogue record or SKU is not an active private record in this workspace.
CATALOG_BUILD_SOURCE_NOT_FOUNDThe source ID is not part of this build.
CATALOG_BUILD_NOT_EDITABLEThe build is terminal or no longer accepts this change.
CATALOG_BUILD_SOURCES_INCOMPLETEA declared PDF has not finished uploading and validating.
CATALOG_BUILD_SOURCES_NOT_SEALEDThe build's sources are not ready for processing.
CATALOG_BUILD_PROCESS_CONFLICTexpectedVersion or the build state changed. Read the build.
CATALOG_BUILD_REVISION_CONFLICTThe issue set changed. Read the build and resolve its current issues.
CATALOG_BUILD_BLOCKEDBlocking issues remain, so the package cannot apply.
CATALOG_BUILD_APPLY_CONFLICTThe catalogue changed while applying. Read the build and follow its actions.
CATALOG_DRIFTA matched or target catalogue record changed since compilation.
STALE_WRITE, LEASE_LOSTAnother request advanced the build. Read the build before retrying.
OPERATION_RUNNINGPackage extraction is already running. Poll the build.
CURVE_PROCESSINGCurve work has not settled. Poll until it does.
CURVE_TOLERANCE_IMPORT_DISABLEDNew curve saves are temporarily disabled; a facts-only waiver still works.
CURVE_EVIDENCE_UNAVAILABLEAccepted curve evidence could not be read during apply. Retry processing if offered.

Curve review and revisions

CodeMeaning
CURVE_JOB_NOT_FOUNDThe build has no curve job for this request.
CURVE_BUILD_NOT_READY, CURVE_BUILD_NOT_EDITABLE, CURVE_BUILD_STALECurve work changed or is not ready. Read the build and use the current review.
CURVE_HANDOFF_CONFLICTCurve work was handed off concurrently. Read the build.
CURVE_SOURCE_UNAVAILABLEThe source PDF for curve work is not available.
CURVE_RESOLUTION_INVALIDA curve operation does not match the current review, fingerprint, or offered responses.
CURVE_REVIEW_ARTIFACT_UNAVAILABLEThe source or overlay image is missing or stale; the curve stays blocked.
TOLERANCE_ASSOCIATION_UNRESOLVED, TOLERANCE_SOURCE_INVALIDThe 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.

CodeMeaning
UPLOAD_UNAUTHORIZEDThe Upload capability is missing, malformed, or for another source.
UPLOAD_EXPIREDThe capability expired. Refresh the upload ticket.
UPLOAD_STALEA newer upload generation exists, or the ticket expired after a failed upload. Refresh the upload ticket.
UPLOAD_CONFLICTAnother upload for this source is in progress or finished with different bytes.
INVALID_MIMEThe request is not Content-Type: application/pdf.
INVALID_SIZEThe body is empty or its size differs from the declared bytes.
UPLOAD_TOO_LARGEThe body is larger than the declared bytes.
UPLOAD_TIMEOUTNo bytes arrived for 60 seconds, or the upload ran past its size-based limit. Retry with the same ticket.
UPLOAD_ABORTEDThe client disconnected mid-upload. Retry with the same ticket.
UPLOAD_STORAGE_UNAVAILABLEStorage did not respond in time. Retry with the same ticket.
UPLOAD_IDENTITY_MISMATCHThe bytes differ from the declared SHA-256.
PDF_VALIDATION_FAILEDThe file is not a readable PDF, for example an HTML download page.
PDF_PAGE_LIMITThe PDF has more than 500 pages.
PDF_VALIDATION_UNAVAILABLEPDF validation is temporarily unavailable. Retry the same upload with the same ticket.
UPLOAD_FAILEDThe 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.

CodeMeaning
NATIVE_ETAP_INVALID_JSONThe manifest or record text is not JSON.
NATIVE_ETAP_SOURCE_MANIFESTThis is the source manifest. Send the collection's points/manifest.json.
NATIVE_ETAP_UNSUPPORTED_FORMATThe files are not a supported ETAP point export.
NATIVE_ETAP_UNSUPPORTED_RECORD_TYPEThe format is recognised, but this record type or model does not import yet.
NATIVE_ETAP_RECORD_NOT_IN_MANIFESTrecordFile does not name exactly one manifest record.
NATIVE_ETAP_HASH_MISMATCHThe record does not match its manifest hashes. Send both files unchanged.
NATIVE_ETAP_IDENTITY_MISMATCHThe manifest entry and record describe different devices or streams.
NATIVE_ETAP_POINT_COUNT_MISMATCHPoint counts differ between the manifest and record.
NATIVE_ETAP_INVALID_EXPORTThe 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.

CodeMeaning
MISSING_FACT, AMBIGUOUS_FACT, CONFLICTING_FACTA 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_UNITA value uses a unit EKX cannot convert.
MULTIPLE_PRODUCTSTwo 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_RESTOREAn 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_UNSUPPORTEDThe import would change a system catalogue record.
SCALAR_CONFLICT, SKU_CONFLICTA saved value or SKU differs from the new evidence.
INVALID_CATALOG_NAMEA 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_COMPATIBILITYA proposed compatibility link is not valid. Answer keep_existing to drop the link with a limitation.
UNSUPPORTED_CATEGORYNo 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_INSUFFICIENTThe sources did not establish enough facts.
SOURCE_DRIFT, CATALOG_DRIFTA 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_IMPACTA change affects devices installed in projects; acknowledge it before apply.
CURVE_CALIBRATION_REQUIREDGraph axes need a calibration correction.
CURVE_ASSOCIATION_REQUIREDA curve needs an explicit association to the target record.
CURVE_APPROVAL_REQUIREDA curve needs the engineer's approval with accept_curve, or rejection.
CURVE_SAVE_LIMIT_EXCEEDEDMore than 20 new curves (500 for native ETAP) were accepted.
CURVE_CONFLICTA new curve conflicts with a saved curve.
CURVE_REVIEW_ARTIFACT_UNAVAILABLEReview images are missing for a curve.
CURVES_REQUIREDA required build has no accepted new curve for a record.
CURVE_EXTRACTION_FAILEDCurve extraction could not finish.
CURVE_GRAPHS_FAILEDSome graphs failed while others reached review. Does not block apply; answer retry_processing to retry only the failed graphs.
CURVE_TOLERANCE_IMPORT_DISABLEDNew curve imports are turned off on this server. continue_without_curves saves the facts when curves are optional.
AI_CREDITS_EXHAUSTEDThe workspace has no AI credits left. Add credits, then answer retry_processing.
RETRYABLE_PROVIDER_FAILURE, RETRYABLE_WORKER_FAILUREA temporary extraction failure; retry when retry_processing is offered.
TERMINAL_PROVIDER_FAILURE, TERMINAL_WORKER_FAILUREExtraction 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:

CodeMeaning
PACKAGE_EXTRACTION_FAILEDFact extraction failed.
PROVIDER_SCHEMA_REJECTEDThe extraction result did not match the required structure.
CURVE_IMPORT_FAILEDCurve import failed for the build.
CANCELLEDThe extraction request was canceled while running.
CANCELED_BY_USERThe build was canceled.
BUILD_EXPIREDThe 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:

CodeMeaning
EKX_TIMEOUTEKX did not answer within 60 seconds. retryable is true.
EKX_UNAVAILABLEEKX could not be reached. retryable is true.
EKX_RESPONSE_UNREADABLEEKX answered without a readable JSON body. retryable is true.
INVALID_EKX_RESPONSEEKX returned a response the tool could not validate.
EKX_REQUEST_FAILEDEKX returned an error without a code.
CURVE_REVIEW_NOT_FOUNDThe 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.