Repository navigation
docs(openapi)!: match the spec to live API behavior - #438
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3f76c08390
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "$ref": "#/components/schemas/link" | ||
| }, | ||
| { | ||
| "$ref": "#/components/schemas/shipment" |
There was a problem hiding this comment.
Allow null reference-number entries in included shipments
The newly supported shipment variant does not describe every payload already documented for this endpoint: the Transport Event response example contains attributes.ref_numbers: [null], while the referenced shipment schema permits only strings. Because the SDK response types are generated from this OpenAPI file, code can treat each entry as a string and fail when this live shape is returned; make the item schema nullable or normalize the response.
AGENTS.md reference: AGENTS.md:L51-L54
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b00a12f. shipments.ref_numbers is a Postgres text array, and the shipment update stores its entries without checking them, so a null entry can be served. The items schema is now nullable, the generated type is (string | null)[], and the Transport Event example validates.
| "$ref": "#/components/schemas/shipment" | ||
| }, | ||
| { | ||
| "$ref": "#/components/schemas/container" |
There was a problem hiding this comment.
Allow null shipment relationships on included containers
The newly supported container variant is too strict for the Container Updated Event example in this same endpoint, where relationships.shipment.data is null; the referenced container schema requires that value to be an object. Generated SDK consumers will therefore receive a non-null relationship type for a payload that can be null and may dereference it unsafely, so the relationship data needs to be nullable.
AGENTS.md reference: AGENTS.md:L51-L54
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in aa1b4e5. Cargo declares belongs_to :shipment, optional: true, so a container can be served with relationships.shipment.data: null. That data is now nullable, and the Container Updated Event example validates.
| "type": "object", | ||
| "allOf": [ | ||
| { | ||
| "$ref": "#/components/schemas/metro_area" | ||
| } | ||
| ], | ||
| "nullable": true |
There was a problem hiding this comment.
Null lookup responses remain invalid
The schema for GET /metro_areas/{id} still rejects the documented data: null response for unknown IDs. The outer nullable: true allows null for its own type, but allOf also requires the value to satisfy metro_area, which only accepts an object. Engineers validating responses against the spec can therefore reject a normal lookup response. Use an endpoint-specific nullable schema that does not also require a non-null object.
Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/openapi.json
Line: 10156-10162
Comment:
**Null lookup responses remain invalid**
The schema for `GET /metro_areas/{id}` still rejects the documented `data: null` response for unknown IDs. The outer `nullable: true` allows null for its own `type`, but `allOf` also requires the value to satisfy `metro_area`, which only accepts an object. Engineers validating responses against the spec can therefore reject a normal lookup response. Use an endpoint-specific nullable schema that does not also require a non-null object.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Fixed. data is now a oneOf of metro_area and a null-only schema. The response has two examples, one with a metro area and one for an unknown ID that returns data: null. Local staging returns data: null with status 200 for an unknown ID.
Add shared BadRequest, Unauthorized, NotFound and UnprocessableEntity
responses and reference them where staging returns those statuses.
Deletes of webhooks, custom fields, definitions and options return 204.
The error object no longer requires title, because Pundit 401s carry
only detail. Types are regenerated.
BREAKING CHANGE: in the exported `paths` type from @terminal49/sdk,
DELETE /webhooks/{id}, /custom_fields/{id},
/custom_field_definitions/{id}, and
/custom_field_definitions/{definition_id}/options/{option_id} answer
204 instead of 200, so their `responses[200]` keys become
`responses[204]`. `components['schemas']['error'].title` becomes
optional.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shipment pod_terminal, destination_terminal and
line_tracking_stopped_by_user are belongs_to links that serialize
{data: null}. Terminal address and code columns are nullable. Raw event
location and vessel can be null, and the container and terminal links
are now documented. Map GeoJSON port country, country_code and
time_zone copy nullable port columns. Types are regenerated.
BREAKING CHANGE: in the exported @terminal49/sdk types, relationship
data, terminal address and code, raw event location and vessel, and
map GeoJSON port country, country_code, and time_zone allow null.
`container_pod_terminal_changed_event` relationships `container`,
`shipment`, and `terminal` move `id` and `type` under a nullable
`data` object, and `type` narrows from string to
'container_pod_terminal_changed_event'. Read
`relationships.container.data?.id` instead of
`relationships.container.id`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ports.latitude and longitude are numeric(13,9) columns. MetroArea reads the same table, and Rails serializes BigDecimal as a string, so both resources return values like "22.57489971". Types are regenerated. BREAKING CHANGE: port and metro area latitude and longitude in the exported @terminal49/sdk types change from number to string, which matches the decimal strings the API returns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Per-resource links.self values, such as data.links.self on a shipment,
are paths like /v2/shipments/{id}, so they use uri-reference. The
top-level links.self on single-resource responses is the absolute
request URL with its query string, so it drops the uri format. On a
create it is the collection URL, not the new resource.
Pagination links come from jsonapi.rb. self repeats the request URL as
sent. The other links CGI-unescape the query and leave page[number]
brackets raw. None of them use the uri format. Pagination links also
include current.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…container included
ContainerSerializer exposes pod_terminal, pickup_facility,
transport_events, and raw_events, and the default include already
returns terminals. GET /containers `included` now allows shipment,
terminal, transport_event, and raw_event items. GET /containers/{id}
`included` gains raw_event, so both endpoints document the same item
types. Types are regenerated.
BREAKING CHANGE: in the exported `paths` type from @terminal49/sdk,
GET /containers `included` widens from `shipment[]` to
`(shipment | terminal | transport_event | raw_event)[]`, so code must
narrow on `type` before it reads shipment attributes. GET
/containers/{id} `included` gains `raw_event`, so an exhaustive
switch over its item `type` must handle raw events.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s edits
TrackingRequestStateMachine has a cancelled state that sets
failed_reason to "Cancelled by user", and the index filter accepts
status=cancelled. Document PATCH /tracking_requests/{id}/cancel, which
returns {} or a 422 whose errors value is a string. PATCH
/tracking_requests/{id} accepts only ref_numbers, which replaces the
list; ref_number was silently ignored. Types are regenerated.
BREAKING CHANGE: in the exported `paths` type from @terminal49/sdk,
PATCH /tracking_requests/{id} takes `ref_numbers: string[]` instead
of `ref_number`. Tracking request `status` adds 'cancelled', and
`failed_reason` adds 'Cancelled by user', 'awaiting_manifest', and
'shipping_line_not_supported'. Exhaustive switches over either field
must handle the new values.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WebhookEventsValidator accepts container.detention_lfd_line.changed, container.delivery_appointment.changed, shipment.routing.filled and link.created, so add them to every webhook event enum and the event catalog. POST /webhooks requires url and a non-empty events list (the validator rejects an empty list with 422). active is optional because the webhooks.active column defaults to true. Types are regenerated. BREAKING CHANGE: in the exported `paths` type from @terminal49/sdk, POST /webhooks requires `events` instead of `active`, which becomes optional. Webhook `events`, webhook notification `event`, and the other webhook event enums gain values, so exhaustive switches over them must handle the new members. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SearchResultSerializer has no set_type, so every row is typed search_result. The matched kind is attributes.entity_type, which is shipment, cargo or tracking_request. Types are regenerated. BREAKING CHANGE: in the exported `paths` type from @terminal49/sdk, GET /search result `type` is 'search_result' instead of 'shipment' | 'cargo' | 'tracking_request', and `attributes.entity_type` is 'shipment' | 'cargo' | 'tracking_request' instead of a string. Read the kind from `attributes.entity_type`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GET /webhook_notifications/examples renders one serializable_hash, so data is a single webhook_notification and there are no links or meta. included carries the shipment, container, port, terminal, vessel and event records the postman INCLUDE_MAP names. event is required: a missing or unavailable event returns 400. The example payload page no longer shows a top-level links object, which delivered payloads lack. Types are regenerated. BREAKING CHANGE: in the exported `paths` type from @terminal49/sdk, GET /webhook_notifications/examples requires `query.event` and returns one `data: webhook_notification` with `included: webhook_notification_included[]`, instead of a `webhook_notification[]` list with `links` and `meta`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…types
For customer accounts the top-level /custom_field_definitions list
returns Terminal49 templates and create returns 401. Document GET and
POST /accounts/{account_id}/custom_field_definitions, which serve the
account's own definitions and accept template_id. Definitions accept
entity_type Shipment or Cargo, and the custom_fields filter matches
Shipment, Cargo or TrackingRequest exactly. POST /custom_fields accepts
a tracking_request entity. The per-entity custom_fields lists accept
include=definition,entity,updated_by. Options are not paginated.
Reference pages now describe the template routes, the 422 on deleting
a definition that has data, and the account-only api_slug lookup.
Types and SDK reference pages are regenerated. To keep the SDK compiling, customFields.list
types its entityType filter from the GET /custom_fields query, and the
two custom field list queries keep accepting page params until the SDK
stops sending them.
BREAKING CHANGE: in the exported `paths` type from @terminal49/sdk,
custom field definition `entity_type` is 'Shipment' | 'Cargo' in
requests and responses; 'Container' and 'TrackingRequest' are removed.
`filter[entity_type]` is an enum instead of a string: 'Shipment' |
'Cargo' on GET /custom_field_definitions, and 'Shipment' | 'Cargo' |
'TrackingRequest' on GET /custom_fields. GET /custom_fields and GET
/custom_field_definitions no longer accept `page[number]` or
`page[size]`. Their 200 bodies, and that of GET
/custom_field_definitions/{definition_id}/options, drop `meta`. PATCH
/custom_field_definitions/{id} attributes drop `validation` and
`default_value`, which the API ignores. In the SDK,
customFields.list and customFieldDefinitions.list no longer accept
'Container' as an entityType filter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…manual
DocumentsController#update_params permits only extracted_data_manual,
document_type and classification_notes, so document_type_manual was
dropped silently. Setting document_type marks document_type_source as
customer. The document schema now lists document_type_source, which
the serializer emits, instead of document_type_manual, which it never
did. Validation failures return 422.
Types are regenerated. documents.update sends its documentTypeManual
value as document_type, so the override now takes effect.
BREAKING CHANGE: in the exported `paths` type from @terminal49/sdk,
PATCH /documents/{id} takes `document_type` instead of
`document_type_manual`, and document responses replace
`document_type_manual` with `document_type_source`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApiBaseController rescues ActiveRecord::RecordNotFound with a JSON:API
404 for every action, and staging returned that body for GET
/terminals/{id} and GET /webhooks/{id}, which documented no 404.
Reference the shared NotFound response from each operation that takes
a path parameter and lacked one. GET /metro_areas/{id} is the
exception: an unknown id returns 200 with null data. Its data is a
oneOf of metro_area and a null-only schema, because OpenAPI 3.0
`nullable` beside a $ref still requires the referenced object, and
examples show both responses. Types are regenerated.
BREAKING CHANGE: GET /metro_areas/{id} `data` in the exported
`paths` type from @terminal49/sdk may be null.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PATCH /containers/{id} accepts seal_number, equipment_type,
equipment_length, equipment_height, and weight_in_lbs as well as
ref_numbers (staging containers_controller.rb UPDATE_PARAMS). Document
them with the values the API accepts. equipment_type and
equipment_height are the cargo.rb container_type and container_height
enum keys, and equipment_length is one of EDITABLE_CONTAINER_LENGTHS.
None of the three accepts null, because cargo.rb requires all three
once any of them changes. Document the 401 from the policy check, the
404 from the lookup by ID, and the 422 for a changed number, a failed
validation, or an unknown enum value. Types and SDK reference pages are regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Staging's ContainerSerializer declares has_one :pod_terminal, and cargos.pod_terminal_id is nullable, so a container with no POD terminal yet is served with relationships.pod_terminal.data: null. The live webhook notification examples run failed schema validation on exactly that. The spec typed the data as an object only, while its sibling pickup_facility already allowed null. Types are regenerated, so the exported container type no longer claims the data is always an object. BREAKING CHANGE: a container's relationships.pod_terminal.data in the exported @terminal49/sdk types becomes nullable, so code that reads it must handle null. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OpenAPI 3.0 ignores `nullable` on a schema without `type`, and Ajv
refuses to compile one ("nullable" cannot be used without "type"), so
Spectral's oas3-valid-media-example rule failed on six request
examples that reach custom_field_value. Move `nullable` onto the
string branch of custom_field_value. The regenerated types are
equivalent: custom_field_value still admits null.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ccepted includes
V2::TrackingRequestSerializer declares belongs_to :user, so tracking
requests carry a user relationship and include=user returns the user.
The spec now documents that relationship and the includes the
endpoints accept. Types are regenerated.
BREAKING CHANGE: in the exported `paths` type from @terminal49/sdk,
GET /tracking_requests and GET /tracking_requests/{id} `included` items
can be `user`, so an exhaustive switch over the item `type` must handle
users.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…shipment edits
V2::ShipmentsController#update accepts user_provided_mbl_number and
user_provided_booking_number alongside ref_numbers and tags, but the
PATCH /shipments/{id} request body listed only ref_numbers and tags.
The spec now documents both string attributes. Types are regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cargo declares belongs_to :shipment with optional: true, so a container
with no shipment is served with relationships.shipment.data: null. The
Container Updated Event example on GET /webhook_notifications/{id}
shows exactly that, and it failed validation against the included
container variant. Types are regenerated.
BREAKING CHANGE: a container's relationships.shipment.data in the
exported @terminal49/sdk types becomes nullable, so code that reads it
must handle null.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
shipments.ref_numbers is a Postgres text array, and the shipment
update deserializes ref_numbers without checking its entries, so a
stored entry can be null and is served as null. The Transport Event
example on GET /webhook_notifications/{id} includes a shipment with
ref_numbers: [null], and it failed validation against the included
shipment variant. Types are regenerated.
BREAKING CHANGE: a shipment's attributes.ref_numbers in the exported
@terminal49/sdk types becomes (string | null)[], so code that reads an
entry must handle null.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b00a12fd8e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "description": "The webhook notification event name you wish to see an example of", | ||
| "required": true |
There was a problem hiding this comment.
Require an event in the SDK wrapper
Once this parameter is required, the public SDK remains inconsistent with the documented API: webhookNotifications.examples(event?: string) still accepts no event and client.write-endpoints.test.ts explicitly exercises examples(), which sends an undefined query value and receives the newly documented 400 response. Make the wrapper parameter required and use the generated WebhookEvent type so SDK callers cannot issue this invalid request.
AGENTS.md reference: AGENTS.md:L51-L54
Useful? React with 👍 / 👎.
| "attributes": { | ||
| "type": "object", | ||
| "properties": { |
There was a problem hiding this comment.
Encode the required account-definition attribute alternatives
For the new account-scoped create route, the schema leaves every attribute optional even though the description requires either template_id or all four of entity_type, api_slug, display_name, and data_type; it also leaves the outer request body and data optional. Consequently, the generated post-account-custom-field-definitions type accepts requests such as an empty attributes object that the documented API always rejects with 422. Mark the body/data as required and represent the two valid attribute shapes with oneOf or anyOf.
AGENTS.md reference: AGENTS.md:L51-L54
Useful? React with 👍 / 👎.
Main's spec now declares components.responses (#438), and the rebase's text merge added the trade intelligence block as a second "responses" key in the same object. JSON parsers keep the last key, but YAML-strict tooling such as openapi-typescript rejects the duplicate. Both sets now live in one block. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Main's spec now declares components.responses (#438), and the rebase's text merge added the trade intelligence block as a second "responses" key in the same object. JSON parsers keep the last key, but YAML-strict tooling such as openapi-typescript rejects the duplicate. Both sets now live in one block. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…415) * docs: add Trade Intelligence endpoints to the API reference Documents the nine /v2/trade_intel endpoints (US import bill-of-lading records from US Customs manifests, January 2022 onward) under a public Trade Intelligence tag: request and response schemas as TradeIntel* components, the 400/401/403/422/429/502/504 error bodies, and endpoint descriptions that carry the data caveats (physical containers, estimated values, partial current month, names split by state or withheld, substring company filter, carrier spelling variants). Adds the reference pages to the API Reference navigation and regenerates the Postman collection. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(sdk): add tradeIntel namespace for trade intelligence client.tradeIntel exposes meta, searchCompanies, companyProfile, searchCommodities, topImporters, trends, breakdown, lookupContainer, and lookupBillOfLading. They return the endpoints' plain JSON bodies with types generated from docs/openapi.json, and surface errors through the existing error types: the 403 "not enabled" body maps to FeatureNotEnabledError, 400 and 422 (including FastAPI detail arrays) to ValidationError. The trade intelligence POSTs are read-only queries, so the retry policy now treats POSTs under /trade_intel/ like GETs: 429, 5xx, and network failures are retried with backoff. Other writes still need an Idempotency-Key. Types are generated with --default-non-nullable=false so request fields with server defaults (limit, months, top) stay optional; no existing type changes. Fixture-based tests cover every method (path, method, body, response passthrough) and the 400/422/403/429/502 paths. Regenerates the SDK reference docs and documents the namespace. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: state the real commodity ordering in trade intelligence lookups Lookups sort commodity lines by bill, then by estimated value within each bill, and the 200-line cap applies to that order. The schemas promised a global highest-value-first order, which a multi-bill response does not give. Found in Codex review. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: mark always-returned trade intelligence response fields required meta, the companies/search results, commodities/search results, trend points, and top importers always carry these fields in production, so the generated SDK types no longer make them optional. Fields that are conditional stay optional: a trend point's partial flag, a ranking's estimated_value without hs4, and a profile's details when found is false. Raised in Codex review on the PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore(sdk): regenerate types and reference for required response fields Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: restrict trade intelligence month fields to real calendar months since and until in trends and breakdown accepted any two-digit month, so schema-validating clients and generated Postman examples allowed values like 2026-60. Raised in Codex review on the PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(mcp): trade intelligence tools (#417) * feat(mcp): add trade intelligence tools Seven read-only tools for US ocean import market data, built on the SDK's tradeIntel namespace: search_importers, get_importer_profile, search_commodities, rank_importers, get_trade_trends, get_trade_breakdown, and get_trade_data_coverage. Record-level container and bill of lading lookups stay in the SDK only. The tool descriptions and server instructions carry the caveats agents got wrong without guidance: search ranks by match not size and splits companies by state, the profile with months: 24 is the way to get one company's trend (the trends company filter is a substring match), trends and breakdowns include the incomplete current month, history starts in January 2022, missing volume can mean withheld names, values are estimates, and volumes don't add across products or companies. Inputs are strict (enums for measure, interval, dimensions, and coast; real YYYY-MM months; bounded limits). A 403 returns a plain "not enabled, contact support@terminal49.com" result instead of an error; 400 shows the API's reason, and 429 and 5xx get plain messages without internals. Responses drop internal fields, round numbers, cap long lists, and flag truncation. Adds unit and server tests, eval cases that pass with or without the feature, the new tools in both store listings, and an Unreleased changelog entry. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(mcp): document the trade intelligence tools Adds a trade intelligence section to the MCP tools reference with each tool's parameters, example questions, the per-account enablement note, and the caveats for reading the numbers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(mcp): address Codex review of the trade intelligence tools - A high notify-party share is a clue, not proof of a forwarder: large cargo owners can be mostly notify party, so the descriptions and instructions now say to weigh the name and product mix too. - Name and filter inputs accept up to 200 characters, matching the API, so a long company name from search can be reused in later calls. - search_importers reports more_not_shown when it cuts a row's ports, origins, carriers, or products to five, and search_commodities keeps up to 15 common goods and counts the rest in common_goods_not_shown. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(mcp): qualify the notify-party signal and the common goods cap Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(mcp): skip trade intelligence evals where the tools aren't deployed Push-time CI runs the live eval against production, which doesn't list tools a PR adds until it deploys. The new cases now skip when the server under test doesn't list the tool, and still run against the PR preview. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(mcp): describe the search window honestly and score gated evals fairly - search_importers results cover the search index's 12-month window, which can end a month or two before the last full month. The period note, min_containers, and the tool description now say so and point to get_importer_profile for the latest months. - Eval cases for account-gated tools score a "not enabled" reply as the allowed outcome it is, so the scorecard and report no longer count it as a contract failure. Adds unit tests for that scorer. Raised in the Codex and Greptile reviews on the PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(mcp): spell out TEU and HS, and describe the search window Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(mcp): cap trend series by whole periods and breakdowns by whole levels Trend points arrive ordered by period then group, so cutting the flat series at 500 dropped the newest periods and could split one, while the reported range still claimed them. The series now keeps the most recent whole periods, reports the range actually returned, and counts the earlier periods left out. Breakdowns drop whole deeper levels instead of cutting a parent's children. Raised in Codex review on the PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(mcp): describe default windows and min_containers precisely The trends and breakdown defaults run from about two years (or one year) back through the current, incomplete month, inclusive, so they are not a clean 24 or 12 months. Descriptions now say so and tell agents to set since and until for annual totals. min_containers applies to the search window in period, not the last 12 full months. Raised in Codex review on the PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(mcp): describe the trends and breakdown default windows precisely Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(mcp): keep a capped trend's range in one period format A capped quarterly or yearly series reported a range mixing a point label (2026-Q1) with a month boundary (2026-06). period now always holds the requested month range, and periods_shown gives the first and last periods actually returned, in the series' own labels. Raised in Codex review on the PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore: regenerate SDK types, SDK reference docs, and Postman collection Regenerated once on top of main after the rebase (main's SDK resync in Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: state which trade intelligence dimensions can't be combined Lane dimensions (pol, pol_country, container_type) can't be combined with company or product dimensions in one request, and estimated_value isn't available by them; the API answers 400. The MCP tool descriptions said so but the API reference didn't. Raised in Codex review on the PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: merge trade intelligence responses into the shared responses block Main's spec now declares components.responses (#438), and the rebase's text merge added the trade intelligence block as a second "responses" key in the same object. JSON parsers keep the last key, but YAML-strict tooling such as openapi-typescript rejects the duplicate. Both sets now live in one block. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(sdk): let tradeIntel calls infer their types from the spec Transport.execute now infers the response body type from the typed openapi-fetch call (#439), so the explicit body type arguments no longer fit its signature. The inferred types are the same TradeIntel* schemas. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore: regenerate SDK types, SDK reference docs, and Postman collection Regenerated on top of main after #438 and #439. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: require exact-length trade intelligence state, SCAC and HS4 codes State filters (company_state, dest_state, state) now require two letters, the scac filter four letters, and top importers hs4 four digits, matching the MCP tool schemas. maxLength alone let values like "F" or "123" through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(sdk): keep the RetryInterceptor doc comment on the class The retry explanation sat above the new ReplayableRequest and RetryRequestParams declarations, so the generated reference lost it. Move it back above RetryInterceptor and say that read-only trade intelligence query POSTs are retried like GETs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: regenerate SDK reference docs and Postman collection Regenerated after the trade intelligence code patterns and the RetryInterceptor doc comment move. SDK types and MCP filter schemas are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: Auto-generate Postman collection from openapi.json [skip ci] * docs(mcp): say estimated_value is not available by lane The API rejects estimated_value with pol, pol_country or container_type as a dimension or filter (docs/openapi.json TradeIntelDimension and TradeIntelFilters). The MCP guide presented estimated_value as generally available, so it now states the restriction for get_trade_trends and get_trade_breakdown and lists the lane combination rules with the other trade intelligence caveats. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mcp): warn that estimated_value is not available by lane The measure and dimension descriptions for get_trade_trends and get_trade_breakdown now say that estimated_value cannot be used with pol, pol_country or container_type, which the API answers with a 400. A protocol test checks the advertised measure description and the MCP docs section for both tools. Addresses the Codex review on packages/mcp/src/tools/trade-intel.ts:82. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(mcp): count all 21 tools in the README, overview and setup guide The PR registers seven trade intelligence tools, but the MCP README header and table, the TOOLS_OVERVIEW summary and the setup guide's "list the tools" check still said 14. The README table now lists the trade intelligence tools too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(mcp): keep documented tool counts and the README table on tools/list Fails when the README, TOOLS_OVERVIEW or the setup guide state a tool count other than the tools/list length, or when the README tool table misses or adds a tool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(mcp): require the lane-filter case in the estimated_value warning The API rejects estimated_value when pol, pol_country or container_type is used at all, as a dimension or as a filter. The test now fails if the tool description or the docs drop the filter case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(mcp): pin the trade trends and breakdown default windows Without since, the trade-intel service returns the N months ending with until (24 for trends, 12 for breakdown), so since is N - 1 months before until. The test fails while tools/list, docs/mcp/home.mdx or the OpenAPI spec describe the default as "N months ago" or "about a year back". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(trade-intel): state the trends and breakdown default windows exactly Without since, the API returns the N months ending with until (24 for trends, 12 for breakdown), so since defaults to N - 1 months before until. The since fields said "Defaults to 24/12 months ago", which reads as N + 1 calendar months. The breakdown and trends descriptions and the MCP guide now give the same window. Source: analytics._range in the trade-intel service. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: Auto-generate Postman collection from openapi.json [skip ci] * fix(mcp,sdk): describe the trade default windows as the API computes them get_trade_trends and get_trade_breakdown said "about two years back" and "about a year back, not a clean 12 months". They now say the window is the 24 or 12 months ending with the current, incomplete month, with since 23 or 11 months before until. The SDK JSDoc, regenerated types and SDK reference match the OpenAPI source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(openapi): match the trade intelligence contract to what the service returns Checked against Terminal49/t49 staging (apps/trade-intel build.py, analytics.py, api.py, index.py): - meta dates (since_month, until_month_exclusive, facts_since_month, partial_month, refreshed_since) and company first_month/last_month are first-of-month dates, YYYY-MM-DD; facts_until_exclusive is the day after the build; mode is full or incremental. - importers/top: states can be null, estimated_value is always sent and is null without hs4, and filters echo port_of_discharge as pod. - companies/profile: until is absent when found is false, and totals.states can be null. - trends periods are always YYYY-MM (the first month of the quarter or year), not YYYY-Qn or YYYY. - a 422 can also be the query cost-cap error, { error }. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(sdk): type trade intelligence nulls and keep retries within maxRetries Regenerated types from the corrected spec, so TradeIntelTopImporter states and estimated_value, profile totals.states and profile until are typed as the service sends them, ranking filters expose pod, and the 422 body includes the cost-cap { error } variant. Type tests in spec-types.test.ts fail against the old types. Fixtures now use the service's real formats (YYYY-MM-DD meta dates, YYYY-MM quarterly periods), and the live smoke test expects facts_since_month as a first-of-month date. RetryInterceptor kept separate retry counters in onError and onResponse, so network failure, network failure, 503, 503, success made five requests with maxRetries 2. Both paths now draw on one per-request budget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: Auto-generate Postman collection from openapi.json [skip ci] * fix(mcp): read the trade index dates the service actually sends The service sends meta and search-window dates as YYYY-MM-DD, but previousMonth() only accepted YYYY-MM, so get_trade_data_coverage lost last_full_month and search_window.to, and search_importers lost period.to. Index dates are now accepted in either form and shown as YYYY-MM, including first_month and last_month. rank_importers also leaves out estimated_value_usd when the service sends null instead of emitting an undefined key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Why
#433 merged before its review and live-test fixes landed. We ran every SDK, MCP, and CLI operation against a local backend built from the latest staging (51711be). Many spec claims did not match what the API returns. This PR fixes the spec so it describes the API as it behaves today.
What changed
Scope
This PR is spec-first. It also carries the SDK edits that keep each commit compiling against the regenerated
terminal49.ts, plus the regenerated SDK reference pages. Each commit body names its edits.Tradeoffs
The spec documents current behavior even where the backend is wrong. Backend fixes are tracked in Linear: DEV-13512, DEV-13513, DEV-13514, DEV-13515, DEV-13516, DEV-13632.
Blast Radius
Commits marked
!change generated SDK types: response unions, nullability, required fields, and route shapes. Code that relied on the old, inaccurate types may need updates. The Postman collection is left for the CI bot to regenerate.Verification
vp run checkandvp run testpass at every commit (git rebase -x).generate:types, SDK docs, and MCP filter generation show no drift at the tip.docs/openapi.json.Stack: this PR, then SDK fixes, then the MCP and CLI fixes on top of the SDK fixes.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.The PR appears safe to merge, with one non-blocking correction needed for null metro-area responses.
Fix with agent prompt
Summary
Updates the OpenAPI spec, generated TypeScript types, and reference pages to describe the live API.
Acknowledged by
dodeja: the PR intentionally documents current backend defects rather than fixing them, including invalid enum writes, authorization responses, and cancellation of unknown IDs. Further SDK, MCP, and CLI fixes are deferred to later PRs.Reviews (1) · Last reviewed commit: "docs(openapi): document the user-provide..." · Reviewed by Greptile