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>
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