Repository navigation
feat(sdk)!: sync the SDK with the OpenAPI spec - #433
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: 42c97b01e6
ℹ️ 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".
"Edit a container" was documented as PATCH /containers. The live API
answers PATCH /containers/{id} (401 unauthenticated) and 404s on
PATCH /containers, so move the operation under /containers/{id}.
Types are regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the operations the SDK was missing: - documents, email submissions, and document schemas managers - container, shipment, and tracking-request custom field update/delete - container and shipment party roles - container update, shipping line get, webhook events and trigger, party create/update, and custom field list filters Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Webhooks, webhook notifications, vessels, ports, terminals, parties, metro areas, and custom field resources now call transport.client with literal spec paths, so the compiler checks path params, query keys, and bodies. Pagination goes through applyTypedPagination, which clamps page[size]. endpoint() and listQuery() are gone. Caller-owned payloads on pre-existing methods meet the spec type through one callerPayload() cast. New methods take spec-derived types: parties create/update take the request body, and the trigger and list filter params derive from the spec. Three routes stay on executeManual because the spec does not match what the pre-existing signatures send: vessel future positions (spec requires query params the methods never took) and custom field options list (spec omits the page params the CLI forwards). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ager
Containers, shipments, and tracking requests call their own literal spec
paths (container_id, shipment_id, tracking_request_id) instead of the
shared string-URL helpers, and entity-subresources.ts is deleted.
Methods added in the previous commit are now strict. updateCustomField
and the tracking request setCustomField take the spec custom_field_value
type. addPartyRole takes { role, partyId } with role derived per route,
so containers accept only pickup_dray_carrier as the spec documents. The
hand-written PartyRoleName export is dropped. containers.update takes the
spec attributes and loses its as-any cast.
The custom_fields list GETs stay on executeManual: include=definition is
absent from the spec.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The legacy GET /containers/{id}/route endpoint is gone from the API.
map_geojson is its replacement, so containers.route() now calls it
through the typed client and keeps the "route" name users know.
The raw format is the GeoJSON FeatureCollection. The mapped format is a
new ContainerRoute: ports in ports_sequence order (name, country code,
POL/POD/TS label, coordinates, inbound eta/ata, outbound etd/atd), the
current vessel when it is at sea, and totalPorts. Absent properties map
to null. Track LineStrings stay in the raw format only.
containers.map() becomes a deprecated alias of route(), and
getContainerRoute() keeps delegating to route(). The mapping tests use
a fixture copied from the spec example.
BREAKING CHANGE: the exported Route type and mapRoute are removed. The
mapped result of containers.route() / getContainerRoute() is now
ContainerRoute ({ ports, currentVessel, totalPorts }) instead of Route
({ id, totalLegs, locations, createdAt, updatedAt }). Port code/city,
transport mode, carrier SCAC and per-leg vessels are no longer
available because map_geojson does not return them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… table
GET /vessels/{id}/future_positions requires port_id and
previous_port_id, and future_positions_with_coordinates also requires
latitude and longitude. The SDK methods could not send them, so every
call was malformed. Both now take a params object and use the typed
client. The CLI commands take the matching options.
The CLI table view for t49 containers route lists the route's ports in
order: sequence, POL/POD/TS label, name, country, and arrival and
departure times.
BREAKING CHANGE: vessels.futurePositions(id, options) is now
futurePositions(id, { portId, previousPortId }, options), and
futurePositionsWithCoords(id, options) is now
futurePositionsWithCoords(id, { portId, previousPortId, latitude,
longitude }, options). The getVesselFuturePositions* client methods
change the same way. The CLI commands vessels future-positions and
future-positions-coords now require --port-id and --previous-port-id,
and the coords command also requires --latitude and --longitude.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
get_container_route read the JSON:API document of the retired
GET /containers/{id}/route endpoint, so it could only return NotFound.
It now calls client.containers.route(id, { format: 'mapped' }) and
builds its output from the SDK's ContainerRoute: ports in order with
POL/POD/TS label, country code, ETA/ATA and ETD/ATD, plus the current
vessel's name, IMO, voyage, position, heading, speed and its departure
and arrival ports. Track geometry stays out of the output to keep the
model's context small.
Fields map_geojson cannot source (route_id, port code and city, mode,
carrier SCAC, per-leg vessels) are dropped from the output schema. The
schema now lives beside the formatter and the formatter's return type
is inferred from it, so the two cannot drift.
An empty FeatureCollection returns a NoRouteData soft result. The
feature-gate message points to sales@terminal49.com, as the spec does.
The unused getContainerRouteTool descriptor is removed. Tests and the
live eval use the spec-example fixture; the eval now fails on NotFound
for a known container instead of accepting any soft error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
containers.route() / get_container_route now read
GET /containers/{id}/map_geojson, and the vessel future-position
methods take their required port and coordinate params. Update the SDK
quickstart and methods page (new Vessels section), the MCP tool page
(output fields, NoRouteData, REST equivalent map_geojson), the MCP
guide table, the READMEs and TOOLS_OVERVIEW.
Regenerate docs/sdk/reference and fix docs.json nav: drop the removed
Route page, add ContainerRoute, RoutePort, RouteVessel,
RouteCoordinates and the vessel params pages, plus the pages earlier
commits on this branch added without a nav entry (document managers,
custom field filters, TriggerWebhookParams).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
map() shipped in 0.5.0 returning the map_geojson FeatureCollection in every format. It now fetches through route() and applies no mapper, so its output is unchanged and it is marked deprecated in favor of route(). The CLI map subcommand keeps printing GeoJSON, and its table view no longer renders the GeoJSON as an empty route table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
containers.route.error.json recorded the 404 page from the removed
/containers/{id}/route endpoint, and no test reads it. The capture
script now saves containers route stdout as containers.route.json.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The quickstart still described routes as locations with inbound and outbound legs. It now describes the ordered ports with arrival and departure times and the current vessel. Regenerated the SDK reference for the updated containers.map() JSDoc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Container
An adversarial review of this branch upheld these defects, each by at
least two of three independent reviewers:
- Spec: parties create/update bodies lacked `data.type`; the container
PATCH body lacked `id`/`type`; custom_field_value rejected non-empty
objects. Types are regenerated.
- SDK: trackContainer sent `bill_of_lading` for booking numbers.
webhookNotifications.list/get accept `include`, and the custom field
entityType filter accepts TrackingRequest. containers.map() keeps its
0.5.0 output for every format.
- MCP: get_container_route returns the vessel position timestamp and
the departure and arrival port names.
- CLI: the route table shows the current vessel and handles raw
output; map's README claims match each output mode.
- Tests now assert return values for the new and migrated methods,
and the route, NoRouteData, and trackContainer edge cases.
- Docs: methods.mdx lists every new method, and the removed Route
reference page redirects.
BREAKING CHANGE: trackContainer({ bookingNumber }) now sends
request_type "booking_number". Pass bill of lading numbers with the
new billOfLading input.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SDK sync in #433 removed the manual endpoint helper that client.me() used. /me is in the generated paths now, so go through the typed client like the other single-resource reads. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs(api): document GET /me, the current-credential endpoint Adds the Account tag, the /me path, and the credential, credential_account, credential_user, and feature_availability schemas, with an API reference page and navigation entry. The endpoint is implemented in Terminal49/t49. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(sdk): add client.me() for the current credential Calls GET /me and, in mapped format, returns a CurrentCredential with the account, the signed-in user (or null), the accounts that user can switch to, and the per-feature availability map. Regenerates the OpenAPI types and the SDK reference docs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(mcp): 1.2.0, add the whoami tool whoami reports which credential the session uses, the account it is scoped to (company name, business type, plan, location, tracking slots), the other accounts a signed-in user can switch to, and a per-feature availability map with the reason when a feature is unavailable. No inputs; read-only. Updates the tool enumerations in the test suites and both connector listings. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(mcp): document the whoami tool Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(mcp): expect 14 tools in the protocol smoke checks The stdio and preview protocol smokes hard-code the tool count. whoami brings it to 14; both now also require whoami to be listed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(sdk): require complete credential identity resources * test(docs): verify generated credential account selector * test(sdk): reject a linked credential user without email * fix(sdk): require complete selectable-account linkage * test(sdk): reject omitted selectable account resources * fix(sdk): call GET /me through the typed openapi-fetch client The SDK sync in #433 removed the manual endpoint helper that client.me() used. /me is in the generated paths now, so go through the typed client like the other single-resource reads. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore: regenerate the Postman collection from openapi.json Generated with openapi-to-postmanv2@6.3.0, the version the generate_postman workflow pins, so the bot has nothing to add on push. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore: Auto-generate Postman collection from openapi.json [skip ci] --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* feat(sdk): export the party role, container update, party, and custom field value types
addPartyRole on containers and shipments, containers.update,
parties.create and parties.update, and the setCustomField methods take
types derived from the spec, but none of those types were exported, so
callers could not name what they pass. Export
ContainerAddPartyRoleParams, ShipmentAddPartyRoleParams,
ContainerUpdateAttributes, PartyAttributes, and CustomFieldValue. The
three managers share CustomFieldValue from the models module instead of
each declaring its own copy.
The containers.update test now sends every attribute the spec
documents for PATCH /containers/{id}, and a type test pins the new
exports.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk)!: update documents with documentType and classificationNotes
PATCH /documents/{id} accepts document_type, classification_notes, and
extracted_data_manual. documents.update took documentTypeManual, which
named a field the API never read. It now takes documentType, which
sends document_type, and classificationNotes, which sends
classification_notes and accepts null to clear the notes.
BREAKING CHANGE: documents.update no longer accepts documentTypeManual.
Pass documentType instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk)!: map a tracking request's shipment from tracked_object
The API serializes a tracking request's shipment as the polymorphic
`tracked_object` relationship. The mapper read `shipment` and
`container` relationships that the serializer never sends, so
`mapped.shipment` was always null. It now resolves `tracked_object`:
the included shipment gives the id, bill of lading, and SCAC, and a
shipment that is not included still yields its id.
BREAKING CHANGE: mapped tracking requests no longer have a `container`
key, and `TrackingRequest` no longer declares it. The API never links a
container to a tracking request, so the value was always null.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk)!: limit tracking request includes to tracked_object, customer, user
The tracking request serializer exposes only the tracked_object,
customer, and user relationships. `TrackingRequestInclude` offered
`shipment` and `container`, and the API rejects both with 400.
BREAKING CHANGE: `TrackingRequestInclude` is now
'tracked_object' | 'customer' | 'user'. Replace `include: ['shipment']`
with `include: ['tracked_object']`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk): keep the server's detail on 401 errors
The API answers 401 both for a bad token and for a valid token that the
action's policy denies, such as resume_tracking on a shipment that is
not stopped. The SDK replaced every 401 message with "Invalid or
missing API token", which hid the real reason. AuthenticationError now
carries the server's detail and falls back to the generic message only
when the response has none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk)!: map containers.get and containers.update in mapped and both formats
containers.get and containers.update passed no mapper, so `mapped`
returned the raw JSON:API document and `both` returned it twice. With
`defaultFormat: 'mapped'`, `id`, `number`, and `events` were undefined.
Both now map through mapContainer, like containers.list. getDemurrage
and getRailMilestones read raw attributes, so they now request the raw
format and keep working under a mapped default.
BREAKING CHANGE: containers.get, containers.update, and getContainer
return a mapped `Container` for format `mapped`, and `{ raw, mapped }`
with a mapped `Container` for format `both`. Request format `raw` to
keep the JSON:API document. The CLI defaults to format `mapped`, so
`t49 containers get` now prints the mapped `Container` instead of the
JSON:API document. Pass `--format raw` to keep the old output.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk): keep shipment container ids that are not included
mapShipment built `containers` from the included container resources
only. A shipment fetched without `include=containers` still links its
containers by id, but the mapped shipment showed none. The mapper now
reads `relationships.containers.data`, emits `{ id }` for each linked
container, and adds `number` when the container is included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk)!: map vessels and vessel future positions
vessels.get, getByImo, futurePositions, and futurePositionsWithCoords
passed no mapper, so format `mapped` returned the raw JSON:API document
and `both` returned it twice. They now map to a `Vessel` (name, IMO,
MMSI, last position, speed, and heading). The future-position methods
map to a `VesselWithFuturePositions`, which adds the estimated
`positions` (latitude, longitude, heading, timestamp, estimated).
BREAKING CHANGE: the vessel methods return `Vessel` or
`VesselWithFuturePositions` for format `mapped`, and `{ raw, mapped }`
with the mapped model for format `both`. Request format `raw` to keep
the JSON:API document. The CLI defaults to format `mapped`, so
`t49 vessels get`, `get-by-imo`, `future-positions`, and
`future-positions-coords` now print the mapped model instead of the
JSON:API document. Pass `--format raw` to keep the old output.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk): send stop_tracking and resume_tracking without a body
The spec defines no request body for PATCH /shipments/{id}/stop_tracking
or resume_tracking, and the backend reads only the path id. The SDK sent
a JSON:API document through an `as any` cast. Both calls now send the
path id only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk): filter shipping lines by search on the client
GET /shipping_lines has no search parameter. The SDK sent `search`
through an `as any` cast, the server ignored it, and
listShippingLines('hapag') returned every line. The SDK now requests
the full list and keeps the lines whose SCAC, alternative SCACs, name,
or short name contain the search text, ignoring case. The filter
applies to the raw document too, so every format agrees.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk): search shipping lines whose name columns are null
The spec marks a shipping line's name, short_name, and alternative_scacs
as required strings, but the backing columns are nullable. The search
filter called toLowerCase on each and spread alternative_scacs, so one
line with a null column made listShippingLines(search) throw. The
filter now skips null values.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(sdk): list entity custom fields through the typed client
The spec now documents `include` on GET /containers/{id}/custom_fields,
/shipments/{id}/custom_fields, and /tracking_requests/{id}/custom_fields.
The three customFields() methods drop their hand-built executeManual
URLs and call the typed client with `include: 'definition'`. The request
is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk)!: list custom field options through the typed client
The spec now documents that GET /custom_field_definitions/{id}/options
is not paginated: the API ignores page[number] and page[size] and
returns every option. The SDK sent those params through a hand-built
executeManual URL. It now calls the typed client without them.
customFieldOptions.list and listCustomFieldOptions take CallOptions, so
passing `page` or `pageSize` is a type error instead of a silently
ignored option. `t49 custom-field-options list` drops --page,
--page-size, and --all for the same reason.
BREAKING CHANGE: customFieldOptions.list and listCustomFieldOptions take
CallOptions; page and pageSize no longer compile. t49
custom-field-options list drops --page, --page-size, and --all.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk)!: use the account routes for custom field definitions
GET and POST /custom_field_definitions are the Terminal49 template
routes. For a customer account, list returned the templates (often an
empty list) instead of the account's definitions, and create answered
401. customFieldDefinitions.list and create now call
/accounts/{account_id}/custom_field_definitions. The API scopes that
route to the authenticated account, so the SDK fills account_id from
the client's `accountId` or sends the placeholder `current`.
`list({ scope: 'templates' })` still lists the templates.
BREAKING CHANGE: customFieldDefinitions.list() returns the account's
definitions instead of the Terminal49 templates. Pass
`scope: 'templates'` for the templates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(sdk): cancel tracking requests
The spec now documents PATCH /tracking_requests/{id}/cancel, the one
operation the SDK did not cover. trackingRequests.cancel(id) cancels a
request that is still pending or awaiting a manifest. The API returns
an empty object, and 422 for any other status.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk)!: stop sending page params to unpaginated custom field lists
The spec now documents that GET /custom_fields and the custom field
definition lists are not paginated: the API ignores page[number] and
page[size] and returns every row. customFields.list and
customFieldDefinitions.list no longer send them, matching
customFieldOptions.list. The PaginatedQuery intersections that let the
SDK send undeclared params are gone.
Both methods now take CallOptions instead of ListOptions, so passing
`page` or `pageSize` is a type error instead of a silently ignored
option. CustomFieldDefinitionListOptions extends CallOptions. `t49
custom-fields list` and `t49 custom-field-definitions list` drop
--page, --page-size, and --all, and the live fixture capture script
stops passing --page-size to them.
BREAKING CHANGE: customFields.list, listCustomFields,
customFieldDefinitions.list, and listCustomFieldDefinitions take
CallOptions; page and pageSize no longer compile. t49 custom-fields
list and t49 custom-field-definitions list drop --page, --page-size,
and --all.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk,cli)!: require the event for webhook notification examples
GET /webhook_notifications/examples answers 400 without an event, and
the spec marks the parameter required. The SDK typed it optional and
cast any string past the generated enum, so examples() with no event
always threw ValidationError: Bad Request.
webhookNotifications.examples and getWebhookNotificationExamples now
take a required WebhookEvent, exported from the generated paths, and
pass it through without a cast. The CLI calls examples(), so
webhook-notifications examples makes --event a required option typed
as WebhookEvent. The CLI README documents it, and the live fixture
capture script gets its 400 envelope from an unknown event instead of
a missing one, which the CLI now rejects before calling the API.
BREAKING CHANGE: webhookNotifications.examples(event, options?) and
getWebhookNotificationExamples(event, options?) require event, typed as
the exported WebhookEvent union. Calls without an event, or with a
string outside the documented events, no longer compile. The CLI
command webhook-notifications examples now requires --event.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(sdk): cover a container with no POD terminal
The generated container type accepts relationships.pod_terminal with
data: null, and containers.get maps such a container with null
podTerminal and destinationTerminal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk)!: type tracking request writes from the spec
trackingRequests.update and updateTrackingRequest took any attributes
and cast the body to any, so `{ ref_number: 'X' }` compiled and the API
answered 200 without changing anything. Both now take
TrackingRequestUpdateAttributes, derived from PATCH /tracking_requests/{id}
(`ref_numbers` only). create and inferNumber also send bodies typed from
their operations instead of casting to any. The wire payloads are
unchanged.
BREAKING CHANGE: trackingRequests.update and updateTrackingRequest accept
only `{ ref_numbers?: string[] }`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk)!: infer Transport.execute's result from the openapi-fetch call
execute returned `any` by default, so the typed mappers
(mapContainerRoute, and mapVessel and mapVesselFuturePositions from
earlier in this series) were never checked against their endpoint:
handing containers.route() the vessel mapper still compiled. execute
now returns the data type of the call's success branch, so each mapper
is checked against its route's 200 response, and a bodyless success
such as a 204 DELETE resolves to undefined. The redundant
`ShippingLineResource` annotation in shippingLines.list() goes away
for the same reason.
BREAKING CHANGE: Transport.execute no longer takes a result type
argument; it infers the result from the request it runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk)!: type shipment updates from the spec
shipments.update and updateShipment took any attributes and cast the
body to any, so `{ tags: 'a,b' }` or `{ ref_number: 'X' }` compiled and
went out unchecked. Both now take ShipmentUpdateAttributes, derived
from PATCH /shipments/{id} (`ref_numbers` and `tags`, each a string
list), the same way tracking request updates are typed. The wire
payload is unchanged.
BREAKING CHANGE: shipments.update and updateShipment accept only
`{ ref_numbers?: string[]; tags?: string[] }`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: update the SDK method list for mapped results and corrected routes
methods.mdx now says what `mapped` returns for containers.get and the
vessel methods, lists the includes a tracking request accepts and where
its mapped shipment comes from, and adds trackingRequests.cancel. It
names the account routes that customFieldDefinitions.list and create
call, notes that the custom field lists are not paginated, describes
the client-side shippingLines search, marks the webhook notification
examples event as required, and documents documents.update with
documentType and classificationNotes. Methods without mapping return
the JSON:API response unchanged in `mapped` format.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: describe the 401 AuthenticationError the SDK now throws
AuthenticationError covers every HTTP 401, including an action the API
refuses for the key, and its message carries the API's detail.
AuthorizationError is the HTTP 403 case. The example logs the message
instead of assuming the key is invalid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sdk): send the caller's numberType from createTrackingRequestFromInfer
createFromInfer resolved the type as the inferred number_type first and
the caller's numberType second. infer_number always returns a
number_type, so the option was never used. A booking number that infer
classified as bill_of_lading was submitted as bill_of_lading, and a
retry with numberType after an unrecognized inferred type failed the
same way. The caller's numberType now wins over the inferred value.
A numberType that is not container, bill_of_lading, booking_number, or
booking throws a ValidationError that names it, before any request is
sent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): list custom field definition templates with --templates
customFieldDefinitions.list() now reads the account definitions route
by default and takes scope: 'templates' for the top-level template
route, but custom-field-definitions list had no way to pass the scope.
That left the CLI unable to list the templates whose ids
custom-field-definitions create needs as template_id. list --templates
reads the templates, and list without it still reads the account
definitions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: list client.me() in the SDK method reference
The method reference says it lists every SDK method, but it left out
client.me(), which describes the credential, its account, and the
gated features it can use.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: Auto-generate Postman collection from openapi.json [skip ci]
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* fix(cli): read fixture ids from the list row array
List JSON prints the mapped rows as the data array, with no items key,
so every read_id "data.items.0.*" returned an empty string and the
capture script then ran "shipments get ''". Read data.0.* instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli)!: print one list shape that keeps included in raw and both
`--format raw` on containers, shipments, and tracking-requests list kept
only `body.data`, which dropped `included`. shipping-lines, webhooks, and
webhook-notifications list printed the whole body with no pagination, so
the raw shape depended on the command.
Every list command now goes through listOutput: raw prints the whole
JSON:API document, mapped prints the rows, and both pairs them. The
effective format comes from createClient, so list output no longer
guesses the format from the result's shape. The seven copies of
listDataFrom and paginationFrom are gone.
BREAKING CHANGE: list commands with --format raw print the full JSON:API
document under data, so the rows move from data to data.data (for
example, jq '.data.data[]' instead of jq '.data[]'). With --format both,
data.raw holds the full document instead of its row array. This affects
containers, shipments, tracking-requests, parties, custom-fields,
custom-field-definitions, and custom-field-options list, which printed
only the rows before. shipping-lines, webhooks, and
webhook-notifications list already printed the whole document in raw.
BREAKING CHANGE: webhooks list and webhook-notifications list print the
row array under data in the default mapped format, with links and meta
under pagination. Before, data held the whole JSON:API document, so
jq '.data.data[]' becomes jq '.data[]' and --fields data.id becomes
--fields id. With --format both, data.mapped holds the row array instead
of the whole document, so jq '.data.mapped.data[]' becomes
jq '.data.mapped[]'.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): advertise --all only on lists that can iterate
parties list advertised --all, --max-pages, and --max-rows, then exited
2 with "--all is not supported for this command".
defineList now registers a list's paging flags and its action together:
--page and --page-size only when the API pages the list, and --all only
when the command can iterate. parties list iterates its JSON:API pages
through links.next. The webhooks list help names its page values <n>
like the other lists. The README says which lists page, which iterate,
and which the API returns whole.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): describe shipping-lines --search as a client-side filter
The SDK now filters shipping lines itself because GET /shipping_lines has
no search parameter. The --search help text said it searched "by name or
code", which hid that the CLI fetches every line and that alternative
SCACs and short names also match. The help text and README now say what
matches, and a test pins that the CLI sends no query and prints only the
matching lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): print vessel future positions as a table
`vessels future-positions --table` and `future-positions-coords --table`
printed one row with the vessel id, type, and name and none of the
positions, because the table renderer had no columns for these commands.
They now print the vessel name and IMO, then one row per position with
timestamp, latitude, longitude, heading, and estimated. The renderer reads
the mapped vessel, the `{ raw, mapped }` pair, and the raw document.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): show the columns that identify webhooks, custom fields, and options
Table output fell back to guessed columns for these commands, so webhook
tables showed only ID and Type, custom-field tables lacked the slug and
value, and option tables lacked the label. shipments list printed a Status
column that the mapped shipment never has.
Webhook tables now show URL, active, and the event count. Custom-field
tables show slug and value, plus name and data type when the definition
is included. Option tables show label, value, and position. shipments list
swaps Status for the POD and its ETA. A `{ raw, mapped }` pair now renders
its mapped half even when that half is a JSON:API document.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): honor --table and --fields in config and commands
The config subcommands and `t49 commands` built their formatter from
--json and --compact only, so --table still printed a JSON envelope and
--fields was ignored. They now pass every global output flag to the
formatter. config output renders as key and value rows, and `commands`
renders one row per command group with its subcommands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli)!: parse shipments update --attr values whole and as arrays
`--attr key=value` split on every `=` and kept the first two parts, so
`user_provided_mbl_number=a=b` sent "a". Every value was a string, so
`--attr tags=x` sent a string where PATCH /shipments/{id} expects an array.
Malformed pairs were silently dropped.
Each pair now splits on its first `=`. Values that start with `[`, `{`,
or `"` parse as JSON, and so do true, false, and null on keys other
than ref_numbers and tags. Other values stay strings so long numeric
references keep every digit. ref_numbers and tags are arrays of
strings: plain and repeated values collect into the array, a bare true,
false, or null is a string element, and a JSON value that is not a
string or an array of strings is a usage error. A pair without a key or
with invalid JSON is also a usage error before any request.
BREAKING CHANGE: shipments update --attr sends different values for the
same input. A value after the first `=` is kept whole, so
`--attr key=a=b` sends "a=b" instead of "a". Values starting with `[`,
`{`, or `"`, and true, false, or null on keys other than ref_numbers and
tags, are sent as JSON instead of strings. ref_numbers and tags are
always sent as arrays of strings. A pair without `=`, with an empty key,
or with invalid JSON now exits 2 with a USAGE_ERROR instead of being
dropped. Quote a value as JSON, for example --attr 'key="true"', to send
it as a string.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): accept --max-retries 0 and keep JSON stderr pure
--max-retries used the positive-integer parser, so `--max-retries 0`
exited 2 even though config maxRetries accepts 0 and the SDK treats it as
"no retries". It now takes any non-negative integer.
Usage errors also wrote Commander's "(run --help for usage)" line before
the JSON error, so JSON.parse(stderr) failed. The hint now comes from
printError and only in human-readable output.
The entry point's parse and error handling moved from bin/t49.ts into
main() in src/index.ts, and errors set process.exitCode instead of calling
process.exit(), so piped output flushes and tests run the shipped path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): name the set-custom-field argument api-slug and resolve definition ids
`shipments set-custom-field <id> <field-id>` and the containers variant
sent the second argument as `api_slug`. Passing a definition id, which the
name invited, returned 404.
The argument is now `<api-slug>` in help, `t49 commands`, and the README.
A UUID is resolved to its definition's api_slug with one extra request;
the backend strips everything but letters, digits, and `_` from api_slugs,
so a UUID cannot be a slug.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(cli): pin the webhook-notifications examples request and usage error
webhook-notifications examples requires --event, but no CLI test
covered the option. The new tests assert the exact USAGE_ERROR JSON
envelope with exit status 2 when --event is missing, and the GET
request with its event query when it is set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): fill raw list table columns from the resource attributes
`--table --format raw` on containers, shipments, tracking-requests, and
shipping-lines list printed the ID column and left every other column
blank. The columns read only the mapped camelCase paths, and a raw row
keeps its values under `attributes` in snake_case. Each column now falls
back to its raw attribute, as the custom field columns already do. The
POD Terminal column stays blank in raw, because a raw container row
carries only the terminal's relationship id.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(cli): cover the vessel future-position params and the route table
vessels future-positions must send --port-id and --previous-port-id as
port_id and previous_port_id, and future-positions-coords must reject
an empty --latitude before it sends a request. containers route
--table must print the ports and the current vessel that the SDK maps
from the GET /containers/{id}/map_geojson fixture. No CLI test covered
any of the three.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: describe the mapped container and vessel output in the CLI README
The CLI defaults to format mapped, and containers get and the vessel
commands now print the SDK's mapped model, so JSON output reads
data.number instead of data.data.attributes.number. The README says
which commands changed, how to keep the JSON:API document with
--format raw, and what containers route prints in each format.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(cli): replay the live fixtures through the current list and error output
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli)!: shape single-result output in the resolved format
track, tracking-requests create, infer, and create-from-infer, containers
demurrage and rail, and search shaped their output from the --format
flag alone. Without --format they ignored config defaultFormat, and the
tracking request commands printed the raw JSON:API document. They now
use the resolved format: --format, then config defaultFormat, then
mapped.
BREAKING CHANGE: without --format or a config defaultFormat, t49 track
and t49 tracking-requests create, infer, and create-from-infer print the
mapped result instead of the raw JSON:API document. Pass --format raw to
keep the old output.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test(mcp): run get_container_route through the real SDK client
The tool tests stub the SDK, so nothing checked that the summary the
tool builds matches what containers.route() returns from
GET /containers/{id}/map_geojson. A test now gives the real SDK client
the map_geojson fixture and asserts the request path and the route
summary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): stop sending the container owner prefix as track_container's SCAC
A container prefix (MSKU, ZZQU) names the equipment owner, not the
carrier. track_container passed it to createTrackingRequestFromInfer as
an explicit scac, which overrode the carrier that infer_number selected
(MSKU4330011 went out as MSKU instead of MAEU) and sent an invalid SCAC
when infer had no prediction (ZZQU4330056 got a 422 reported as an
invalid identifier).
Only a caller-supplied scac now reaches the API. When neither inference
nor the caller provides a carrier or number type, the tool returns a
TrackContainerInputError that tells the model what to pass, instead of
the old hint that formatToolError replaced with a generic message. The
pending and 404 results report the SCAC the API recorded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp)!: limit list_tracking_requests status to the API's filter values
The status enum offered succeeded, which the API does not recognize. Its
filter[status] handler treats anything other than created, failed, or
cancelled as pending, so status: succeeded silently returned pending
requests. A tracking request that succeeded has status created.
The enum is now pending, created, failed, and cancelled, typed against
the generated filter[status] parameter so it fails to compile if the
spec adds or drops a value. The descriptions say that created means the
request succeeded and that each item's shipment.id (mapped from
tracked_object) feeds get_shipment_details.
BREAKING CHANGE: list_tracking_requests rejects status: 'succeeded'.
Pass 'created' for requests that succeeded. The new 'cancelled' value
selects cancelled requests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp)!: report why track_container could not create a request
formatToolError turned every 422 from POST /tracking_requests into
"Tracking identifier X is invalid". The live run hit the account's
tracking request limit and told the user the number was invalid, and
the backend's duplicate, tracking_slot_limit_reached, and
insufficient_credits 422s were reported the same way.
track_container now parses the 422 body and classifies it by code
before pointer. A duplicate returns a non-error result with the
existing tracking_request id and shipment_id. Limit and credit refusals
pass the API's message through. Errors that point at request_number,
request_type, number, or scac with an identifier code (blank,
invalid_format, not_recognized, and the like), and infer_number's bare
detail with no code or pointer, say the identifier is invalid. Any
other refusal, such as container_number_tracking_disabled,
no_container_request_type_support, or a party that does not belong to
the account, passes its detail through.
search_container also dropped tracking_request search hits, so a number
with a pending request was tracked again and refused as a duplicate. It
now returns tracking_requests, with shipment_id on a request whose
tracking was stopped, and track_container answers with a request that is still in
progress (pending or awaiting_manifest, the statuses the API treats as
blocking) instead of posting, and reports its real status. The API
scopes its duplicate check by carrier, so when the caller names a SCAC,
an in-progress request under another SCAC (or with none yet) is ignored
and the new request is sent.
BREAKING CHANGE: search_container's output gains a required
tracking_requests array, and total_results now counts containers,
shipments, and tracking requests. track_container returns a result with
tracking_request_created: false and source 'duplicate' or
'search_match' for a number that is already tracked or has an
in-progress request, where it used to fail with an error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): give the track-shipment prompt a check-digit-valid example
track_container rejects CAIU1234567 on the ISO 6346 check digit, so the
prompt's example number now uses MSKU4330011, which passes it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: describe the MCP tracking request statuses and track_container
list_tracking_requests accepts pending, created, failed, or cancelled.
created matches requests that succeeded and are still tracking; a
stopped request has status tracking_stopped, which the filter cannot
select. track_container needs scac when the carrier cannot be inferred
and never uses the container prefix as the SCAC. It returns an existing
container or in-progress (pending or awaiting_manifest) request with
tracking_request_created false before it creates one.
The examples use MSKU4330011, which passes the ISO 6346 check digit;
CAIU1234567 fails it and never reaches the API. The overview examples
drop the request_type argument the tool does not accept and use number
instead of the deprecated containerNumber alias.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: list the real list_tracking_requests parameters in the MCP guide
The MCP guide's tool table listed filters and request_type, which the
strict input schema rejects, and left out request_number and scac.
Match the server.ts schema and docs/mcp/home.mdx.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: list the real list_shipments and list_containers inputs
The MCP guide and TOOLS_OVERVIEW still showed status, port, carrier,
updated_after, and a comma-separated include string. Both tools use
strict input schemas, so those calls fail validation. The guide table
now lists the inputs the schemas accept, and the examples use
voyage_status, current_status, pod_code, shipping_line_scac,
pickup_lfd, and an include array.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: show search_container's tracking_requests and snake_case fields
The example response used camelCase keys the tool never emits and had
no tracking_requests array. Show the real snake_case output, add
pending and failed tracking requests, say that only a request whose
tracking was stopped carries shipment_id, and say total_results counts
all three result types. TOOLS_OVERVIEW.md's Returns line says the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: list summarize_containers, list_parties, search_docs, and whoami in the MCP guides
The MCP guide's tools table, TOOLS_OVERVIEW.md, and the package README
left out tools the server registers. Add summarize_containers,
list_parties, and search_docs to each, add whoami to the package
README, which now counts all 14 tools, and add a search_docs section
to the MCP tools reference.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): return the created request's id and number type from track_container
A new request was described by the submitted number and the locally
inferred type, without the request's id. Every path that creates a
request now returns tracking_request with the id, request_number,
number_type, and scac the API sent back, falling back to the submitted
values for an attribute the response leaves out. That includes the
path that also returns the linked container's details.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): create a tracking request when the matching container is no longer tracked
track_container returned early on any container search hit for the
number. Search returns every container with a shipment, including one
whose shipment stopped tracking, and the API accepts a new request for
such a container because container numbers are reused on later voyages.
It also treated a lone search hit with a different number as a match,
although search matches substrings and reference numbers.
track_container now checks for an in-progress tracking request first.
It then reads every container whose number matches exactly, in search
order, because a reused number can match a stopped container and a
tracked one. It returns the first whose shipment has no
line_tracking_stopped_at. Otherwise it sends the request
and lets the API's duplicate check decide. get_container's shipment
summary now includes line_tracking_stopped_at.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: say track_container returns only a container that is still tracked
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: show the real get_container and transport events output in the MCP guide
The get_container and get_container_transport_events examples showed
camelCase fields that the tools never return. Both examples now hold
actual tool output with snake_case keys, and track_container says that
an existing container comes back in the get_container shape.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: use valid shipment filters in the MCP guide's SDK example
The example passed status and carrier to client.shipments.list, which
the SDK rejects before sending a request, and said the call was not
available through MCP although list_shipments is an MCP tool. It now
filters by voyage_status and pod_code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): say track_container returns container details only for a tracked container
The tool description promised detailed container information after it
creates a tracking request. The create response carries the request,
not a container, so a new request returns its id, request_number,
number_type, and scac. The description now says container details come
back only for a container that is still being tracked, and tells the
caller to poll list_tracking_requests and then use search_container or
get_shipment_details.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: show the container number and pickup fields the SDK returns in the MCP guide
The Raw vs Mapped example used attributes.container_number and a mapped
containerNumber and availableForPickup at the top level. The API
serializes the container number as number, and mapContainer returns
number and nests availableForPickup under location.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Lets an agent assign a party (such as the drayage carrier) or set an account custom field on up to 50 containers or shipments, reporting per-record updated, unchanged, failed and not_attempted results. The tools call the SDK methods #433 and #439 added (partyRoles, addPartyRole, removePartyRole, setCustomField, deleteCustomField). The party_role list is parsed in the tool, since the SDK returns it unmapped. set_custom_field accepts an array of option values for multi-select fields and treats the same options in another order as unchanged. A call stops starting new records after 20s, under the 30s /mcp maxDuration, and lists the records it did not reach in not_attempted. The server now lists 16 tools; the protocol smoke scripts and tests expect that count and the two new names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Why
The SDK fell out of sync with the API. About half of the API had no SDK method, and many methods built URLs by hand, so the compiler could not catch drift.
What changed
PATCH /containers/{id}, the route the live API serves, and adds the missingdata.typeto the parties bodies. The SDK now covers all 89 spec operations, including documents, email submissions, party roles, per-entity custom fields, and webhook triggers. Calls go through the typedopenapi-fetchclient.containers.route()now reads route v2 (GET /containers/{id}/map_geojson). The mapped format returns aContainerRoutesummary of ports and the current vessel.containers.map()is deprecated and still returns GeoJSON. The MCP toolget_container_routeuses the same summary.vessels.futurePositionsandfuturePositionsWithCoordssend the query parameters that the spec requires.trackContainer({ bookingNumber })sendsbooking_numberinstead ofbill_of_lading, and a newbillOfLadinginput covers bills of lading.Scope
include=definitionand the page parameters. A spec follow-up can move them.containers routeneeds a capture with a real token.Tradeoffs
callerPayloadcast holds that trust. New methods take spec-derived types.Blast Radius
Breaking for SDK users of the old
Routetype, the mappedroute()shape, the vessel future-position signatures, andtrackContainercallers who passed bill of lading numbers asbookingNumber. The commits declare each break. The old/routeendpoint returns 404 and the vessel calls could not send required parameters, so those breaks affect no working caller. The MCP server does not calltrackContainer, so MCP tracking is unchanged. CLI users ofvessels future-positions*must pass the new required options.Verification
vp run checkexits 0 andvp run testpasses on every commit. At the tip: SDK 334, MCP 400, CLI 31.map_geojsonexample.🤖 Generated with Claude Code