Skip to content

Add Search v2 endpoint documentation - #180

Merged
dodeja merged 4 commits into
mainfrom
docs/search-v2-endpoint
Jul 2, 2026
Merged

Add Search v2 endpoint documentation#180
dodeja merged 4 commits into
mainfrom
docs/search-v2-endpoint

Conversation

@dodeja

@dodejadodeja commented Mar 3, 2026

Copy link
Copy Markdown
Member

Documents the GET /search endpoint in the API reference docs.

Changes

  • openapi.json — Added /search GET endpoint with full schema, parameters, response examples, error codes
  • search.mdx — New MDX page for the Search endpoint
  • docs.json — Added Search navigation group

Details

  • Query parameter: query (required, string)
  • Max 25 results (no pagination)
  • Searches: Shipments, Containers, Tracking Requests
  • Full-text search via PostgreSQL websearch_to_tsquery + ILIKE fallback

Linear: DEV-8837

Created by Trin 🥷

Greptile Summary

This PR documents the GET /search endpoint in the API reference, adding its OpenAPI spec entry, a Mintlify MDX page, and navigation support. It also ships a substantial OAuth 2.1 authentication layer for the hosted MCP endpoint: a new workos-jwt.ts module for local RS256 JWT verification (with JWKS caching), a remote fallback via an internal principal endpoint, RFC-compliant WWW-Authenticate challenge headers on all 401 responses, and updated MCP documentation covering the new dual-mode auth flow.

Key observations:

  • The new /search OpenAPI path is well-structured and consistent with existing JSON:API response shapes, but the "Search" tag is absent from the x-tagGroups array, which will leave the endpoint ungrouped in tools like Redoc or Stoplight.
  • fetchJwks in packages/mcp/src/auth/workos-jwt.ts lacks a request timeout, unlike verifyViaInternalPrincipal which enforces a 2-second abort. A slow or unreachable JWKS endpoint could stall the serverless function.
  • The 401 response on /search is documented with a description only; adding a minimal body schema would keep it consistent with the 400 entry.
  • The sdks/typescript-sdk/src/client.ts change correctly handles Bearer-prefixed tokens passed as apiToken, which is needed for OAuth tokens forwarded from the MCP layer.

The documentation additions are accurate and the MCP OAuth implementation is logically sound with good test coverage and proper fallback handling. Two small gaps should be addressed: the missing request timeout on fetchJwks is a real reliability risk in a serverless environment, and the "Search" tag omission from x-tagGroups is a documentation completeness issue.

Confidence Score: 4/5

  • Safe to merge with minor follow-up: address the missing JWKS fetch timeout and add the Search tag to x-tagGroups before significant production OAuth traffic.
  • The documentation additions are accurate and well-structured. The MCP OAuth implementation is logically sound with good test coverage and proper fallback handling. Two small gaps prevent a perfect score: the missing request timeout on fetchJwks is a real reliability risk in a serverless environment, and the "Search" tag omission from x-tagGroups is a documentation completeness issue that affects endpoint discoverability in OpenAPI tools.
  • packages/mcp/src/auth/workos-jwt.ts (missing fetch timeout in fetchJwks) and docs/openapi.json (Search tag absent from x-tagGroups, 401 response missing body schema)

Sequence Diagram

sequenceDiagram
participant Client
participant MCP as api/mcp.ts
participant WorkOS as workos-jwt.ts
participant Internal as Internal Principal API
participant T49API as Terminal49 API
Client->>MCP: POST /mcp with Authorization Bearer token
MCP->>MCP: extractAuthorizationToken → scheme detection
alt No token present
MCP-->>Client: 401 with WWW-Authenticate challenge
else Token has three dot-segments (JWT-like bearer)
MCP->>WorkOS: verifyWorkosJwt(token)
WorkOS->>WorkOS: fetchJwks cached 10 minutes
WorkOS->>WorkOS: verifySignature + validClaims
alt Local verification succeeds
WorkOS-->>MCP: VerifiedPayload authSource oauth_local
MCP->>T49API: Forward with Bearer token
T49API-->>MCP: API response
MCP-->>Client: 200 MCP response
else Local verification returns null
WorkOS-->>MCP: null
MCP->>Internal: POST token_principal with 2s timeout
alt Remote verification active
Internal-->>MCP: active true with user and account ids
MCP->>T49API: Forward with Bearer token
T49API-->>MCP: API response
MCP-->>Client: 200 MCP response
else Remote verification fails
Internal-->>MCP: error or inactive
MCP-->>Client: 401 with WWW-Authenticate challenge
end
end
else Legacy token scheme or no dots
MCP->>MCP: isMatchingClientSecret check
alt Secret matches
MCP->>T49API: Forward with configured API token
T49API-->>MCP: API response
MCP-->>Client: 200 MCP response
else Mismatch
MCP-->>Client: 401 with WWW-Authenticate challenge
end
end
Loading

Last reviewed commit: 66f98f3

@vercel

vercelBot commented Mar 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
apiReadyReadyPreview, CommentJul 1, 2026 10:40pm

Request Review

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

17 files reviewed, 6 comments

Edit Code Review Agent Settings | Greptile

Comment threaddocs/openapi.json Outdated
"example": {
"data": [
{
"id": "abc12345-6789-0def-ghij-klmnopqrstuv",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example id values contain non-hexadecimal characters (letters g through s). Valid UUIDs use only hex digits [0-9a-f], but these examples use invalid characters. This may confuse API consumers reading the documentation.

Replace with valid UUID examples using only hexadecimal characters [0-9a-f]. Real Terminal49 resource IDs follow the standard 8-4-4-4-12 hex format.

Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/openapi.json
Line: 7165
Comment:
The example `id` values contain non-hexadecimal characters (letters g through s). Valid UUIDs use only hex digits [0-9a-f], but these examples use invalid characters. This may confuse API consumers reading the documentation. Replace with valid UUID examples using only hexadecimal characters [0-9a-f]. Real Terminal49 resource IDs follow the standard 8-4-4-4-12 hex format.
How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps

Copy link
Copy Markdown
Additional Comments (2)

docs/docs.json
The MCP navigation references "mcp/project-overview", but no corresponding docs/mcp/project-overview.mdx file exists. This will cause a broken navigation link or build failure in Mintlify.

Either create the missing docs/mcp/project-overview.mdx file or remove this entry from the pages array.

Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/docs.json
Line: 204
Comment:
The MCP navigation references `"mcp/project-overview"`, but no corresponding `docs/mcp/project-overview.mdx` file exists. This will cause a broken navigation link or build failure in Mintlify.
Either create the missing `docs/mcp/project-overview.mdx` file or remove this entry from the pages array.
How can I resolve this? If you propose a fix, please make it concise.

sdks/typescript-sdk/src/client.ts
The search method accepts an empty query string and forwards it to the API, which returns a 400 Bad Request. Unlike other methods in the class (e.g., inferTrackingNumber on line 290-293), this lacks early validation and causes an unnecessary network round-trip.

Add a guard at the start of the method:

if(!query||query.trim()===''){thrownewValidationError('query is required (/query)');}
Prompt To Fix With AI
This is a comment left during a code review.
Path: sdks/typescript-sdk/src/client.ts
Line: 205-208
Comment:
The `search` method accepts an empty query string and forwards it to the API, which returns a `400 Bad Request`. Unlike other methods in the class (e.g., `inferTrackingNumber` on line 290-293), this lacks early validation and causes an unnecessary network round-trip.
Add a guard at the start of the method:
```typescriptif (!query||query.trim() ==='') {
thrownewValidationError('query is required (/query)');
}
```
How can I resolve this? If you propose a fix, please make it concise.

Comment threadpackages/mcp/src/auth/workos-jwt.ts Outdated
Comment on lines +125 to +128
const response = await fetch(jwksUrl, {
method: 'GET',
headers: { Accept: 'application/json' },
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetchJwks has no request timeout

The fetch call here has no AbortController or timeout, while the sibling verifyViaInternalPrincipal consistently uses a 2-second abort timeout. In a serverless/Vercel environment, if the WorkOS JWKS endpoint is slow or temporarily unreachable, this call will block the function until the platform-level timeout (potentially tens of seconds), rejecting every OAuth token during that window.

Consider adding the same pattern used in verifyViaInternalPrincipal:

Suggested change
constresponse=awaitfetch(jwksUrl,{
method: 'GET',
headers: {Accept: 'application/json'},
});
constabortController=newAbortController();
consttimeout=setTimeout(()=>abortController.abort(),3000);
letresponse: Response;
try{
response=awaitfetch(jwksUrl,{
method: 'GET',
headers: {Accept: 'application/json'},
signal: abortController.signal,
});
}catch{
return[];
}finally{
clearTimeout(timeout);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/mcp/src/auth/workos-jwt.ts
Line: 125-128
Comment:
`fetchJwks` has no request timeout
The `fetch` call here has no `AbortController` or timeout, while the sibling `verifyViaInternalPrincipal` consistently uses a 2-second abort timeout. In a serverless/Vercel environment, if the WorkOS JWKS endpoint is slow or temporarily unreachable, this call will block the function until the platform-level timeout (potentially tens of seconds), rejecting every OAuth token during that window.
Consider adding the same pattern used in `verifyViaInternalPrincipal`:
```suggestion const abortController = new AbortController(); const timeout = setTimeout(() => abortController.abort(), 3000); let response: Response; try { response = await fetch(jwksUrl, { method: 'GET', headers: { Accept: 'application/json' }, signal: abortController.signal, }); } catch { return []; } finally { clearTimeout(timeout); }```
How can I resolve this? If you propose a fix, please make it concise.

Comment threaddocs/openapi.json
Comment on lines +8517 to +8519
"401": {
"description": "Unauthorized \u2014 missing or invalid API token"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

401 response missing body schema

The 400 response is fully documented with a JSON:API error schema and example, but the 401 response only carries a description string with no content entry. API consumers generating client code or running type-safe validation will see an untyped 401. For consistency with the rest of the spec, consider adding a minimal schema:

"401": {
"description": "Unauthorized — missing or invalid API token",
"content": {
"application/vnd.api+json": {
"schema": {
"type": "object",
"properties": {
"errors": {
"type": "array",
"items": { "type": "object", "properties": { "detail": { "type": "string" } } }
}
}
}
}
}
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/openapi.json
Line: 8517-8519
Comment:
`401` response missing body schema
The `400` response is fully documented with a JSON:API error schema and example, but the `401` response only carries a description string with no `content` entry. API consumers generating client code or running type-safe validation will see an untyped `401`. For consistency with the rest of the spec, consider adding a minimal schema:
```json"401": {
"description": "Unauthorized — missing or invalid API token",
"content": {
"application/vnd.api+json": {
"schema": {
"type": "object",
"properties": {
"errors": {
"type": "array",
"items": { "type": "object", "properties": { "detail": { "type": "string" } } }
}
}
}
}
}
}
```
How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps

Copy link
Copy Markdown
Additional Comments (1)

docs/openapi.json
"Search" tag not added to x-tagGroups

The new /search endpoint uses the tag "Search", but the x-tagGroups array only lists the tags "Shipments", "Containers", "Custom Field Definitions", "Custom Field Options", "Custom Fields", "Tracking Requests", "Webhooks", "Webhook Notifications", and "Metro Areas". Tools that respect x-tagGroups (e.g., Redoc, Stoplight) will not surface the Search endpoint under any named group — it would appear ungrouped or be hidden entirely.

Add "Search" to the "End Points" group:

 "x-tagGroups": [
{
"name": "End Points",
"tags": [
"Shipments",
"Containers",
"Custom Field Definitions",
"Custom Field Options",
"Custom Fields",
"Tracking Requests",
"Search",
"Webhooks",
"Webhook Notifications",
"Metro Areas"
]
},
Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/openapi.json
Line: 8524-8537
Comment:
`"Search"` tag not added to `x-tagGroups`
The new `/search` endpoint uses the tag `"Search"`, but the `x-tagGroups` array only lists the tags `"Shipments"`, `"Containers"`, `"Custom Field Definitions"`, `"Custom Field Options"`, `"Custom Fields"`, `"Tracking Requests"`, `"Webhooks"`, `"Webhook Notifications"`, and `"Metro Areas"`. Tools that respect `x-tagGroups` (e.g., Redoc, Stoplight) will not surface the `Search` endpoint under any named group — it would appear ungrouped or be hidden entirely.
Add `"Search"` to the `"End Points"` group:
```suggestion "x-tagGroups": [ { "name": "End Points", "tags": [ "Shipments", "Containers", "Custom Field Definitions", "Custom Field Options", "Custom Fields", "Tracking Requests", "Search", "Webhooks", "Webhook Notifications", "Metro Areas" ] },```
How can I resolve this? If you propose a fix, please make it concise.

dodeja added 2 commits July 1, 2026 14:01
Terminal49Client#search forwarded an empty/whitespace query straight
to the API, which just round-trips a 400. Validate client-side like
the other write helpers (e.g. trackContainer) do.
@dodeja

Copy link
Copy Markdown
MemberAuthor

Status update while bringing this branch up to date with main:

Rebase / conflicts resolved

  • This branch actually carries two commits: the intended "Add Search v2 endpoint documentation" work, and an earlier "Harden hosted MCP OAuth docs..." commit. That OAuth commit predates (Feb 28) and is fully superseded by the WorkOS AuthKit gateway that shipped on main via Add WorkOS MCP auth gateway #240 (merged June 21) and hardened in fix(mcp): annotate tools with readOnlyHint so reads aren't treated as edit tools #280Draft improvements from assistant conversations: event location nulls & event filtering #284 — it reimplements local JWT verification with hardcoded URLs that conflict with the now-canonical resource.ts single-source-of-truth pattern, and every env var / behavior it adds (T49_MCP_TOKEN_VERIFY_URL, application/vnd.api+json, etc.) has a superseding equivalent already on main. I dropped it rather than reapply it — reapplying would regress the shipped OAuth implementation and reintroduce the exact per-file resource derivation CLAUDE.md warns against.
  • The Search docs commit itself only ever touched docs/openapi.json, docs/docs.json, and the new search.mdx page — a clean, non-overlapping addition once rebased onto current main.

Why CI was red
generate-postman-collection, mcp, and Mintlify Deployment were all failing together, which lines up with the PR being unmergeable (DIRTY) rather than three unrelated bugs — GitHub's PR-merge-ref checkout fails wholesale when a PR can't auto-merge. After rebasing cleanly:

  • npm run test --workspace @terminal49/mcp -- --run → 130/130 passing, npm run build clean, oxlint clean.
  • npm run test --workspace @terminal49/sdk -- --run → all passing, build + oxlint/oxfmt clean.
  • spectral lint couldn't run here (the remote ruleset URL in .spectral.mjs 404s from this environment), but npx mintlify broken-links reports no broken links.
  • Postman collection regenerated cleanly from the fixed spec (openapi2postmanv2 -s docs/openapi.json ...); diff is the new Search folder plus the usual random-UUID churn on existing items.

Review feedback addressed

  • Added "Search" to x-tagGroups and top-level tags (Greptile/greptile-apps).
  • Added a proper 401 body schema to /search, matching the 400 shape (Greptile).
  • Fixed the example id values in the /search 200 response — they used non-hex letters (g–s), not valid hex UUIDs (Greptile).
  • Terminal49Client#search now throws ValidationError on an empty/whitespace query instead of round-tripping a guaranteed 400, matching the trackContainer pattern; added a test (Greptile).
  • The docs.json broken-link comment (mcp/project-overview) and the fetchJwks missing-timeout comment both point at code that only existed in the dropped OAuth commit — moot once that commit is gone.
  • Also normalized the new /search response media type to application/json to match the rest of the spec (it was application/vnd.api+json, inconsistent with all 115 other responses).

Where things stand
The rebase, docs fixes, Postman regen, and SDK fix are ready in the branch's working tree; two independent commits (chore: auto-generate Postman collection..., fix(sdk): reject empty search query...) are made. The actual docs/OpenAPI commit is blocked locally by an unrelated, non-versioned pre-commit hook on my machine that still references a docs/openapi/*.yaml bundler workflow retired in October 2025 (commit a180701) — it errors on any commit touching docs/openapi.json regardless of content. I'll finish landing that commit and push once that's sorted out on my end; flagging here so the status is accurate in the meantime.

Adds /search API reference (docs/openapi.json + docs.json nav) with a
proper Search tag group, 401 response schema, and valid example UUIDs.
@dodeja
dodejaforce-pushed the docs/search-v2-endpoint branch from 66f98f3 to e4d9b9dCompareJuly 1, 2026 22:39
@dodeja

Copy link
Copy Markdown
MemberAuthor

Docs commit landed (e4d9b9d) after removing a stale local pre-commit hook that referenced a retired OpenAPI YAML bundler. Pushed and CI re-triggered.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:e4d9b9d33b

ℹ️ 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".

Comment threaddocs/openapi.json
Comment on lines +10382 to +10383
"/search": {
"get": {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Regenerate SDK OpenAPI types

Adding /search to the OpenAPI source without regenerating sdks/typescript-sdk/src/generated/terminal49.ts leaves the SDK's exported paths type out of sync; rg '"/search"' sdks/typescript-sdk/src/generated/terminal49.ts still finds no generated path. Consumers who rely on the published generated OpenAPI types will not be able to type this newly documented endpoint, so include the generate:types output with this OpenAPI change.

Useful? React with 👍 / 👎.

@dodeja
dodeja merged commit 03c70d1 into mainJul 2, 2026
7 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@dodeja