diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..43fd5a73 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,15 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/debian +{ + "name": "Development", + "image": "mcr.microsoft.com/devcontainers/typescript-node:latest", + "features": { + "ghcr.io/devcontainers/features/node:1": {} + }, + "postCreateCommand": "yarn install", + "customizations": { + "vscode": { + "extensions": ["esbenp.prettier-vscode"] + } + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..448b262e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,100 @@ +name: CI +on: + push: + branches-ignore: + - 'generated' + - 'codegen/**' + - 'integrated/**' + - 'stl-preview-head/**' + - 'stl-preview-base/**' + pull_request: + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' + +jobs: + lint: + timeout-minutes: 10 + name: lint + runs-on: ${{ github.repository == 'stainless-sdks/terminal49-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Bootstrap + run: ./scripts/bootstrap + + - name: Check types + run: ./scripts/lint + + build: + timeout-minutes: 5 + name: build + runs-on: ${{ github.repository == 'stainless-sdks/terminal49-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Bootstrap + run: ./scripts/bootstrap + + - name: Check build + run: ./scripts/build + + - name: Get GitHub OIDC Token + if: github.repository == 'stainless-sdks/terminal49-typescript' + id: github-oidc + uses: actions/github-script@v6 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Upload tarball + if: github.repository == 'stainless-sdks/terminal49-typescript' + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + run: ./scripts/utils/upload-artifact.sh + + - name: Upload MCP Server tarball + if: github.repository == 'stainless-sdks/terminal49-typescript' + env: + URL: https://pkg.stainless.com/s?subpackage=mcp-server + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + BASE_PATH: packages/mcp-server + run: ./scripts/utils/upload-artifact.sh + test: + timeout-minutes: 10 + name: test + runs-on: ${{ github.repository == 'stainless-sdks/terminal49-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Bootstrap + run: ./scripts/bootstrap + + - name: Build + run: ./scripts/build + + - name: Run tests + run: ./scripts/test diff --git a/.github/workflows/deploy_postman.yml b/.github/workflows/deploy_postman.yml deleted file mode 100644 index bc13c624..00000000 --- a/.github/workflows/deploy_postman.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Deploy Postman Collection - -on: - push: - branches: - - main - workflow_dispatch: # Add this to enable manual triggering - -jobs: - deploy-postman-collection: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install jq (for Postman API call) - run: sudo apt-get update && sudo apt-get install -y jq - - - name: Deploy to Postman API - env: - POSTMAN_API_KEY: ${{ secrets.POSTMAN_API_KEY }} - POSTMAN_COLLECTION_UID: 5900de09-f05a-4528-8b12-9ad1d0477023 - run: | - echo "On main branch with changes to collection - deploying to Postman API..." - - # Verify collection file exists (it should, but check anyway) - if [ ! -f "Terminal49-API.postman_collection.json" ]; then - echo "Error: Collection file not found!" - exit 1 - fi - - # Construct the JSON payload - JSON_PAYLOAD=$(jq --null-input --rawfile collection "Terminal49-API.postman_collection.json" '{ "collection": ($collection | fromjson) }') - - # Check if jq succeeded - if [ $? -ne 0 ]; then - echo "Error: jq failed to process the collection file. Is it valid JSON?" - exit 1 - fi - - # Save the payload to a file instead of trying to pass it directly - echo "$JSON_PAYLOAD" > postman_payload.json - - # Use the file with curl instead of passing the data directly - curl --location --fail --request PUT "https://api.getpostman.com/collections/$POSTMAN_COLLECTION_UID" \ - --header 'Content-Type: application/json' \ - --header "X-Api-Key: $POSTMAN_API_KEY" \ - --data @postman_payload.json - - if [ $? -eq 0 ]; then - echo "✅ Postman collection update request sent successfully." - else - echo "❌ Error: Failed to update Postman collection via API." - exit 1 - fi \ No newline at end of file diff --git a/.github/workflows/generate_postman.yml b/.github/workflows/generate_postman.yml deleted file mode 100644 index 3447278c..00000000 --- a/.github/workflows/generate_postman.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Generate Postman Collection - -on: - push: - paths: - - 'docs/openapi.json' # Only trigger on changes to this file - -jobs: - generate-postman-collection: - runs-on: ubuntu-latest - permissions: - contents: write # Needed for committing back to the repo - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Full history for proper diffing - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install openapi-to-postmanv2 - run: npm install -g openapi-to-postmanv2 - - - name: Generate Postman Collection - run: | - echo "Generating Postman collection from OpenAPI spec..." - openapi2postmanv2 -s docs/openapi.json -o Terminal49-API.postman_collection.json -p -O folderStrategy=Tags - - - name: Commit changes - id: commit - run: | - git config --global user.name 'github-actions[bot]' - git config --global user.email 'github-actions[bot]@users.noreply.github.com' - git add Terminal49-API.postman_collection.json - if git diff --quiet --cached; then - echo "No changes detected in generated collection." - echo "committed=false" >> $GITHUB_OUTPUT - else - echo "Generated collection changed. Committing..." - git commit -m "chore: Auto-generate Postman collection from openapi.json [skip ci]" - echo "committed=true" >> $GITHUB_OUTPUT - fi - - - name: Push changes - if: steps.commit.outputs.committed == 'true' - run: | - echo "Pushing updated collection to ${{ github.ref_name }}..." - git push origin HEAD:${{ github.ref_name }} - \ No newline at end of file diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml new file mode 100644 index 00000000..3c4ed8f3 --- /dev/null +++ b/.github/workflows/release-doctor.yml @@ -0,0 +1,21 @@ +name: Release Doctor +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + release_doctor: + name: release doctor + runs-on: ubuntu-latest + if: github.repository == 'Terminal49/API' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') + + steps: + - uses: actions/checkout@v4 + + - name: Check release environment + run: | + bash ./bin/check-release-environment + env: + diff --git a/.gitignore b/.gitignore index e43b0f98..d62bea50 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,12 @@ -.DS_Store +.prism.log +node_modules +yarn-error.log +codegen.log +Brewfile.lock.json +dist +dist-deno +/*.tgz +.idea/ +.eslintcache +dist-bundle +*.mcpb diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..7cc13dd1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +CHANGELOG.md +/ecosystem-tests/*/** +/node_modules +/deno + +# don't format tsc output, will break source maps +dist diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..af75adaf --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "arrowParens": "always", + "experimentalTernaries": true, + "printWidth": 110, + "singleQuote": true, + "trailingComma": "all" +} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..d7a87356 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.1.0-alpha.1" +} diff --git a/.spectral.mjs b/.spectral.mjs deleted file mode 100644 index ad97e95e..00000000 --- a/.spectral.mjs +++ /dev/null @@ -1,2 +0,0 @@ -import ruleset from "https://stoplight.io/api/v1/projects/cHJqOjMxNg/spectral.js?branch_slug=feat%2Fcontainer-event-timestamps&token=0049d374-6767-468e-8313-d3f211429e84"; -export default { extends: ruleset }; \ No newline at end of file diff --git a/.stats.yml b/.stats.yml new file mode 100644 index 00000000..4bced4d8 --- /dev/null +++ b/.stats.yml @@ -0,0 +1,4 @@ +configured_endpoints: 34 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/terminal49%2Fterminal49-820338a73a1804825b27092740425a7f325ad0562781df54962355da1c1cc8de.yml +openapi_spec_hash: bad862a83aebd8c2a1fde66d03ffd30b +config_hash: 33ba32a88a9802d4c63096b29ab75ad9 diff --git a/.stoplight.json b/.stoplight.json deleted file mode 100644 index 8359748b..00000000 --- a/.stoplight.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "exclude": [], - "formats": { - "openapi": { - "rootDir": "reference", - "include": [ - "**" - ] - }, - "json_schema": { - "rootDir": "models", - "include": [ - "**" - ] - }, - "markdown": { - "rootDir": "docs" - }, - "image": { - "rootDir": "assets/images" - } - } -} \ No newline at end of file diff --git a/.stoplight/.spectral.yml_original b/.stoplight/.spectral.yml_original deleted file mode 100644 index da4839ca..00000000 --- a/.stoplight/.spectral.yml_original +++ /dev/null @@ -1,4 +0,0 @@ -extends: spectral:oas -rules: - # change this rule's severity to info - operation-description: info diff --git a/.stoplight/styleguide.json b/.stoplight/styleguide.json deleted file mode 100644 index bf16db3f..00000000 --- a/.stoplight/styleguide.json +++ /dev/null @@ -1,2919 +0,0 @@ -{ - "name": "API Docs", - "description": "", - "spectralExtends": [], - "extends": [ - "cHJqOjEyMzU4Ng@32" - ], - "formats": [ - "oas2", - "oas3", - "oas3.0", - "oas3.1" - ], - "aliases": {}, - "rules": {}, - "inheritedRules": [ - { - "id": "rtsOOn6E5uPBWhn6IwC_s", - "given": [ - "#API_Contact" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "url" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "contact-url", - "description": "Contact object should have \"url\"", - "message": "Contact object should have \"url\"." - }, - { - "id": "aHiNDq0i1FDw0XcrdFjxd", - "given": [ - "#API_Contact" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "email" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "contact-email", - "description": "Contact object should have \"email\"", - "message": "Contact object should have \"email\"" - }, - { - "id": "9hbXvUYZsOXEBDEDdrcxc", - "given": [ - "#API_Document" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "info.contact" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "info-contact", - "description": "Info object should have \"contact\" object.", - "message": "Info object should have \"contact\" object." - }, - { - "id": "MZEXAJOk5XdGZc8HoFC2n", - "given": [ - "#API_Document" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "info.description" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "info-description", - "description": "Info object should have \"description\" object.", - "message": "Info object should have \"description\" object." - }, - { - "id": "X1GRUEDf6I3h1mZpt9unO", - "given": [ - "#API_Document" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "info.license" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "info-license", - "description": "Info object should have \"license\" object.", - "message": "Info object should have \"license\" object." - }, - { - "id": "-rYQKrb_N0S_GBd2354lY", - "given": [ - "#API_License" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "url" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "license-url", - "description": "License object should include \"url\".", - "message": "License object should include \"url\"." - }, - { - "id": "67FcJRBKH9k9T52d0zEXq", - "given": [ - "#All_Markdown" - ], - "severity": "warn", - "then": { - "function": "pattern", - "functionOptions": { - "notMatch": "eval\\(" - } - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "no-eval-in-markdown", - "description": "Markdown descriptions must not have \"eval(\".", - "message": "Markdown descriptions must not have \"eval(\"." - }, - { - "id": "-soU2pifTkxrgAFoxT8mm", - "given": [ - "#All_Markdown" - ], - "severity": "warn", - "then": { - "function": "pattern", - "functionOptions": { - "notMatch": "\" tags." - }, - { - "id": "wtXeCEGviQhQxOIXvbLCD", - "given": [ - "#API_Tags" - ], - "severity": "warn", - "then": { - "function": "alphabetical", - "functionOptions": { - "keyedBy": "name" - } - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "openapi-tags-alphabetical", - "description": "OpenAPI object should have alphabetical \"tags\".", - "message": "OpenAPI object should have alphabetical \"tags\"." - }, - { - "id": "sGJnItU7fEIusdGCTLWlL", - "given": [ - "#API_Tags" - ], - "severity": "warn", - "then": { - "function": "schema", - "functionOptions": { - "schema": { - "type": "array", - "minItems": 1 - } - } - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "openapi-tags", - "description": "OpenAPI object should have non-empty \"tags\" array.", - "message": "OpenAPI object should have non-empty \"tags\" array." - }, - { - "id": "QjAVYkImn68plySLI4piX", - "given": [ - "#Operation_Object" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "description" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "operation-description", - "description": "Operation \"description\" should be present and non-empty string.", - "message": "Operation \"description\" should be present and non-empty string." - }, - { - "id": "5rebJe-mdncgeUgnZWBpW", - "given": [ - "#Operation_Object" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "operationId" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "operation-operationId", - "description": "Operation should have \"operationId\".", - "message": "Operation should have \"operationId\"." - }, - { - "id": "y1Q00gw__b0-5wZib8qjm", - "given": [ - "#Operation_Object" - ], - "severity": "error", - "then": { - "function": "pattern", - "functionOptions": { - "match": "^[A-Za-z0-9-._~:/?#\\[\\]@!\\$&'()*+,;=]*$" - }, - "field": "operationId" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "operation-operationId-valid-in-url", - "description": "operationId must not characters that are invalid when used in URL.", - "message": "OperationId should not have characters that are invalid when used in URL." - }, - { - "id": "_v0dDDjvw1RRO9Q0s015G", - "given": [ - "#API_Tags" - ], - "severity": "off", - "then": { - "function": "length", - "functionOptions": { - "max": 1 - } - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "operation-singular-tag", - "description": "Operation should not have more than a single tag.", - "message": "Operation should not have more than a single tag." - }, - { - "id": "IfKvopkFiMA-NyTmBbjNW", - "given": [ - "#Operation_Object" - ], - "severity": "warn", - "then": { - "function": "length", - "functionOptions": { - "max": 999, - "min": 1 - }, - "field": "tags" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "operation-tags", - "description": "Operation should have non-empty \"tags\" array.", - "message": "Operation should have non-empty \"tags\" array." - }, - { - "id": "E6CL8FyWvfTLAIp68ozYJ", - "given": [ - "#Path_Item" - ], - "severity": "error", - "then": { - "function": "pattern", - "functionOptions": { - "notMatch": "{}" - }, - "field": "@key" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "path-declarations-must-exist", - "description": "Path parameter declarations must not be empty, ex.`/given/{}` is invalid.", - "message": "Path parameter declarations must not be empty, ex.\"/given/{}\" is invalid." - }, - { - "id": "TimanzYsFPF_Ki9902tdw", - "given": [ - "#API_Contact" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "name" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "contact-name", - "description": "Contact object should have \"name\"", - "message": "Contact object should have \"name\"" - }, - { - "id": "4WLv-b-t8eNNX6HwDCq67", - "given": [ - "#Path_Object" - ], - "severity": "warn", - "then": { - "function": "pattern", - "functionOptions": { - "notMatch": ".+\\/$" - }, - "field": "@key" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "path-keys-no-trailing-slash", - "description": "Path should not end with slash.", - "message": "Path should not end with slash." - }, - { - "id": "KpItt5LBEhRVGPpEs_JbR", - "given": [ - "#Path_Object" - ], - "severity": "warn", - "then": { - "function": "pattern", - "functionOptions": { - "notMatch": "\\?" - }, - "field": "@key" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "path-not-include-query", - "description": "Path should not include query string.", - "message": "Path should not include query string." - }, - { - "id": "8K1RuU8kBHxZk3bXuRXFO", - "given": [ - "#API_Tags_Item" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "description" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "tag-description", - "description": "Tag object should have `description`.", - "message": "Tag object should have \"description\"." - }, - { - "id": "yXz2pH6ekJWqhxDZzjbvj", - "given": [ - "#API_Server" - ], - "severity": "warn", - "then": { - "function": "schema", - "functionOptions": { - "schema": { - "type": "array", - "minItems": 1, - "items": { - "type": "object" - } - }, - "dialect": "draft7" - } - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "api-servers", - "description": "OpenAPI document should have a server defined. \n\nThis can be localhost, a development or production server. \n\n**OpenAPI V3 example**\n\n```json\n{\n \"servers\": [\n {\n \"url\": \"https://staging.myprodserver.com/v1\",\n \"description\": \"Staging server\"\n },\n {\n \"url\": \"https://myprodserver.com/v1\",\n \"description\": \"Production server\"\n }\n ]\n}\n```\n\n**OpenAPI V2 example**\n\n```json\n{\n \"host\": \"myprodserver.com\",\n \"basePath\": \"/v2\",\n \"schemes\": [\n \"https\"\n ]\n}\n```\n\n", - "message": "Server should be present." - }, - { - "id": "sNqeHjPS61BO3qsRi2II4", - "given": [ - "#API_Server_URL" - ], - "severity": "warn", - "then": { - "function": "pattern", - "functionOptions": { - "notMatch": "/$" - } - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "server-trailing-slash", - "description": "Server URL should not have trailing slash.", - "message": "Server URL should not have trailing slash" - }, - { - "id": "U48hEoI-xAtcB-UveCQ1M", - "given": [ - "#Operation_Object" - ], - "severity": "warn", - "then": { - "function": "oasOpSuccessResponse", - "field": "responses" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "operation-success-response", - "description": "Operation should have at least one \"2xx\" or \"3xx\" response.", - "message": "Operation should have at least one \"2xx\" or \"3xx\" response." - }, - { - "id": "g5IQNeU8RWEJy9-xCOn2N", - "given": [ - "#API_Document" - ], - "severity": "error", - "then": { - "function": "oasPathParam" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "path-params", - "description": "Path parameters must be defined and valid.", - "message": "{{error}}" - }, - { - "id": "MVabHGhr00QHwJD8ugkF6", - "given": [ - "#Operation_Object" - ], - "severity": "warn", - "then": { - "function": "oasOpParams", - "field": "parameters" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "operation-parameters", - "description": "Operation parameters are unique and non-repeating.", - "message": "Operation parameters are unique and non-repeating." - }, - { - "id": "jETMZRa1EPP2BtKcM7jiI", - "given": [ - "#All_Enum" - ], - "severity": "warn", - "then": { - "function": "typedEnum" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "typed-enum", - "description": "Enum values should respect the specified type.", - "message": "{{error}}" - }, - { - "id": "WypkfKdIRZmqf8vEUNSGM", - "given": [ - "#API_Document" - ], - "severity": "error", - "then": { - "function": "oasDocumentSchema" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oas2-schema", - "description": "Validate structure of OpenAPI v2 specification.", - "message": "{{error}}", - "formats": [ - "oas2" - ] - }, - { - "id": "iW306HYeYLNETMLsqde83", - "given": [ - "#API_Document" - ], - "severity": "error", - "then": { - "function": "oasDocumentSchema" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oas3-schema", - "description": "Validate structure of OpenAPI v3 specification.", - "message": "{{error}}", - "formats": [ - "oas3" - ] - }, - { - "id": "hY0Z_3rB0fsOwiEdkignm", - "given": [ - "#API_Document" - ], - "severity": "warn", - "then": { - "function": "oasUnusedComponent" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oas3-unused-component", - "description": "Validate structure of OpenAPI v3 specification.", - "message": "{{error}}", - "formats": [ - "oas3" - ] - }, - { - "id": "AwdnjByzCRPyK-224GS7E", - "given": [ - "#API_Document" - ], - "severity": "error", - "then": { - "function": "oasOpIdUnique" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "operation-operationId-unique", - "description": "Every operation must have unique operationId.", - "message": "Every operation must have unique operationId" - }, - { - "id": "-1OyeCaEcG9KFYK-bqCrX", - "given": [ - "#Operation_Object" - ], - "severity": "error", - "then": { - "function": "oasOpFormDataConsumeCheck" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oas2-operation-formData-consume-check", - "description": "Operations with \"in: formData\" parameter must include \"application/x-www-form-urlencoded\" or \"multipart/form-data\" in their \"consumes\" property.", - "message": "Operations with \"in: formData\" parameter must include \"application/x-www-form-urlencoded\" or \"multipart/form-data\" in their \"consumes\" property.", - "formats": [ - "oas2" - ] - }, - { - "id": "0BVvUSHP61eiu4qTIOSUi", - "given": [ - "#API_Document" - ], - "severity": "warn", - "then": { - "function": "oasTagDefined" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "operation-tag-defined", - "description": "Operation tags must be defined in global tags.", - "message": "Operation tags must be defined in global tags" - }, - { - "id": "dYY-cXMN8t4EMZ4Ci33z1", - "given": [ - "#All_Ref" - ], - "severity": "error", - "then": { - "function": "refSiblings" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "no-$ref-siblings", - "description": "Property must not be placed among $ref", - "message": "{{error}}", - "formats": [ - "oas3.0", - "oas2" - ] - }, - { - "id": "hUChSKy2gRJgTgNFWhUZl", - "given": [ - "#API_Document" - ], - "severity": "warn", - "then": { - "function": "oasOpSecurityDefined", - "functionOptions": { - "schemesPath": [ - "securityDefinitions" - ] - } - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oas2-operation-security-defined", - "description": "Operation \"security\" values must match a scheme defined in the \"securityDefinitions\" object.", - "message": "{{error}}", - "formats": [ - "oas2" - ] - }, - { - "id": "ePdmsYk8D5aLoLBQUOEAN", - "given": [ - "#API_Document" - ], - "severity": "warn", - "then": { - "function": "oasOpSecurityDefined", - "functionOptions": { - "schemesPath": [ - "components", - "securitySchemes" - ] - } - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oas3-operation-security-defined", - "description": "Operation `security` values must match a scheme defined in the `components.securitySchemes` object.", - "message": "{{error}}", - "formats": [ - "oas3" - ] - }, - { - "id": "J2-3CFt974nXGbJT8Em-X", - "given": [ - "#All_Enum" - ], - "severity": "warn", - "then": { - "function": "oasSchema", - "functionOptions": { - "schema": { - "type": "array", - "uniqueItems": true - } - }, - "field": "enum" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "duplicated-entry-in-enum", - "description": "Enum values should not have duplicate entry.", - "message": "{{error}}" - }, - { - "id": "IzzuTJ45K-Q6wa6nrZNk2", - "given": [ - "#API_Document" - ], - "severity": "error", - "then": { - "function": "schema", - "functionOptions": { - "schema": { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - }, - "dialect": "draft7" - }, - "field": "schemes" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oas2-api-schemes", - "description": "OpenAPI host \"schemes\" must be present and non-empty array.", - "message": "OpenAPI host \"schemes\" must be present and non-empty array", - "formats": [ - "oas2" - ] - }, - { - "id": "T9CkRySm3cZFPU4jBTfF4", - "given": [ - "#API_Document" - ], - "severity": "error", - "then": { - "function": "oasDiscriminator", - "field": "definitions[?(@.discriminator)]" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oas2-discriminator", - "description": "Discriminator property must be defined and required", - "message": "Discriminator property must be defined and required", - "formats": [ - "oas2" - ] - }, - { - "id": "fB5AU2JQJXvXz4k2Xca11", - "given": [ - "#API_Server_URL" - ], - "severity": "warn", - "then": { - "function": "pattern", - "functionOptions": { - "notMatch": "example.com" - } - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "server-not-example", - "message": "Server URL must not point at example.com." - }, - { - "id": "_NUBHV5bOhXzqsVT9sA8w", - "given": [ - "#Request_Parameter_All" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "description" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "parameter-description", - "message": "Parameter objects must have \"description\"." - }, - { - "id": "cfA6slTJB9MbOmV2QP_l7", - "given": [ - "#API_Document_RecursiveSearch" - ], - "severity": "warn", - "then": { - "function": "undefined", - "field": "anyOf" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oas2-anyOf", - "description": "anyOf is not available in OpenAPI v2, it was added in OpenAPI v3", - "message": "anyOf is not available in OpenAPI v2, it was added in OpenAPI v3", - "formats": [ - "oas2" - ] - }, - { - "id": "q2GwBoYZdR8WPdac6PIdH", - "given": [ - "#API_Document_RecursiveSearch" - ], - "severity": "warn", - "then": { - "function": "undefined", - "field": "oneOf" - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oas2-oneOf", - "description": "anyOf is not available in OpenAPI v2, it was added in OpenAPI v3", - "message": "oneOf is not available in OpenAPI v2, it was added in OpenAPI v3", - "formats": [ - "oas2" - ] - }, - { - "id": "zmXTmGj4rm7949P12Qbip", - "given": [ - "#All_Example" - ], - "severity": "warn", - "then": { - "function": "xor", - "functionOptions": { - "properties": [ - "externalValue", - "value" - ] - } - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oas3-examples-value-or-externalValue", - "description": "Examples must have either \"value\" or \"externalValue\" field.", - "message": "Examples must have either \"value\" or \"externalValue\" field.", - "formats": [ - "oas3" - ] - }, - { - "id": "ukmU3b3B53uRrMIJ8qL3G", - "given": [ - "#All_Example_Schema" - ], - "severity": "error", - "then": { - "function": "oasExample", - "functionOptions": { - "oasVersion": "2", - "schemaField": "$", - "type": "schema" - } - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oas2-valid-schema-example", - "description": "Examples must be valid against their defined schema.", - "message": "{{error}}", - "formats": [ - "oas2" - ] - }, - { - "id": "b0iaCoPvp9MifwHtrysor", - "given": [ - "#All_Example_Schema" - ], - "severity": "error", - "then": { - "function": "oasExample", - "functionOptions": { - "oasVersion": "3", - "schemaField": "$", - "type": "schema" - } - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oas3-valid-schema-example", - "description": "Examples must be valid against their defined schema.", - "message": "{{error}}", - "formats": [ - "oas3" - ] - }, - { - "id": "fHMah3Nu2R-0v5Lx_Xpif", - "given": [ - "#All_Example_Media" - ], - "severity": "error", - "then": { - "function": "oasExample", - "functionOptions": { - "oasVersion": "2", - "schemaField": "schema", - "type": "media" - } - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oas2-valid-media-example", - "description": "Examples must be valid against their defined schema.", - "message": "{{error}}", - "formats": [ - "oas2" - ] - }, - { - "id": "I78rlWABxBQz8e7p1ZmU3", - "given": [ - "#All_Example_Media" - ], - "severity": "error", - "then": { - "function": "oasExample", - "functionOptions": { - "oasVersion": "3", - "schemaField": "schema", - "type": "media" - } - }, - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oas3-valid-media-example", - "description": "Examples must be valid against their defined schema.", - "message": "{{error}}", - "formats": [ - "oas3" - ] - } - ], - "inheritedTargets": [ - { - "id": "TL4BXx4ZdvDSzpeseqkqN", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "The complete API specification document. This can be used to target any part of the OpenAPI document using **field**.\n\n*Use this if you don't find specific targets that cater to your usecase.* ", - "name": "API_Document", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$" - }, - { - "formats": [ - "oas3" - ], - "given": "$" - } - ] - }, - { - "id": "tLNb4KQK2_P-wE4kz1leM", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "The top level description in an API document", - "name": "API_Description", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.info.description" - }, - { - "formats": [ - "oas3" - ], - "given": "$.info.description" - } - ] - }, - { - "id": "nv2gMgSWv8A-RHIABOHCp", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "The complete operation object. Use it in combo with field object.", - "name": "Operation_Object", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "#Path_Item[get,put,post,delete,options,head,patch,trace]" - }, - { - "formats": [ - "oas3" - ], - "given": "#Path_Item[get,put,post,delete,options,head,patch,trace]" - } - ] - }, - { - "id": "ZnH-fhAd-k9Fv-IGetoCe", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "Responses for all operations including get, put, post, delete, options, head, patch, trace.", - "name": "Operation_Responses", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "#Operation_Object.responses" - }, - { - "formats": [ - "oas3" - ], - "given": "#Operation_Object.responses" - } - ] - }, - { - "id": "t1yT5lLXC1DGNqXA14LPx", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "Path_Item", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.paths[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.paths[*]" - } - ] - }, - { - "id": "29HcMweXXXjxo4sxKEc2Y", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "The top level description in an API document", - "name": "API_Contact", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.info.contact" - }, - { - "formats": [ - "oas3" - ], - "given": "$.info.contact" - } - ] - }, - { - "id": "DWwyU8wh4oPnumv1PZ0Hs", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "The top level description in an API document", - "name": "API_License", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.info.license" - }, - { - "formats": [ - "oas3" - ], - "given": "$.info.license" - } - ] - }, - { - "id": "DBZ0eW1LpbV2xVJYWlQte", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "All markdown descriptions across the document.", - "name": "All_Markdown", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..[description,title]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..[description,title]" - } - ] - }, - { - "id": "or7KSIuFpijgOoU8yzVtC", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "Tags on an API object", - "name": "API_Tags", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.tags" - }, - { - "formats": [ - "oas3" - ], - "given": "$.tags" - } - ] - }, - { - "id": "12IYf_nEWbivGfGXSx8Mq", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "API hosts defined in the API specification", - "name": "API_Server", - "targets": [ - { - "formats": [ - "oas3" - ], - "given": "$.servers" - }, - { - "formats": [ - "oas2" - ], - "given": "$.host" - } - ] - }, - { - "id": "vQC6yLYQgwxXxWffD8aET", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "All responses (object) in an API", - "name": "Response_All_Object", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.responses" - }, - { - "formats": [ - "oas2" - ], - "given": "#Operation_Responses" - }, - { - "formats": [ - "oas3" - ], - "given": "$.components.responses" - }, - { - "formats": [ - "oas3" - ], - "given": "#Operation_Responses" - }, - { - "formats": [ - "oas3" - ], - "given": "$..responses" - }, - { - "formats": [ - "oas2" - ], - "given": "$..responses" - } - ] - }, - { - "id": "71cDIsToI0o_NWAake1LY", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "API host urls defined in the API specification", - "name": "API_Server_URL", - "targets": [ - { - "formats": [ - "oas3" - ], - "given": "$.servers[*].url" - }, - { - "formats": [ - "oas2" - ], - "given": "$.host" - } - ] - }, - { - "id": "7luCURmf7g-B0LVISQPCZ", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "All references throughout the API", - "name": "All_Ref", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..[?(@property === '$ref')]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..[?(@property === '$ref')]" - } - ] - }, - { - "id": "W4flLE_OHMzCcundZSz-w", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "All references throughout the API", - "name": "All_Enum", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..[?(@ && @.enum && @.type)]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..[?(@ && @.enum && @.type)]" - } - ] - }, - { - "id": "8wxrtPD2K-WuJMW0zWvR2", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "All request parameters", - "name": "Request_Parameter_All", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..parameters[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..parameters[*]" - } - ] - }, - { - "id": "QhBWl9TIoq7BVyHKzjx8I", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "All request query parameters", - "name": "Request_Parameter_Query", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..parameters[?(@.in==\"query\")]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..parameters[?(@.in==\"query\")]" - } - ] - }, - { - "id": "bca3yWBRKow6Z6TGyHFpB", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "All request header parameters", - "name": "Request_Parameter_Header", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..parameters[?(@.in==\"header\")]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..parameters[?(@.in==\"header\")]" - } - ] - }, - { - "id": "auLQbTGWnMW_RKgYTEhau", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "All request cookie parameters", - "name": "Request_Parameter_Cookie", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..parameters[?(@.in==\"cookie\")]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..parameters[?(@.in==\"cookie\")]" - } - ] - }, - { - "id": "SmpeyXkQyyNdT3wVYRsGx", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "All request path parameters", - "name": "Request_Parameter_Path", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..parameters[?(@.in==\"path\")]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..parameters[?(@.in==\"path\")]" - } - ] - }, - { - "id": "xC_6dhuowS8pTJIiS9qhW", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "Path object. Usually used to target the Path key e.g. `/users/{userId}`", - "name": "Path_Object", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.paths" - }, - { - "formats": [ - "oas3" - ], - "given": "$.paths" - } - ] - }, - { - "id": "KaYLHeUMWKww9qDGRSqVq", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "All examples for schemas", - "name": "All_Example_Schema", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..definitions..[?(@property !== 'properties' && @ && (@.example !== void 0 || @['x-example'] !== void 0 || @.default !== void 0) && (@.enum || @.type || @.format || @.$ref || @.properties || @.items))]" - }, - { - "formats": [ - "oas2" - ], - "given": "$..parameters..[?(@property !== 'properties' && @ && (@.example !== void 0 || @['x-example'] !== void 0 || @.default !== void 0) && (@.enum || @.type || @.format || @.$ref || @.properties || @.items))]" - }, - { - "formats": [ - "oas2" - ], - "given": "$..responses..[?(@property !== 'properties' && @ && (@.example !== void 0 || @['x-example'] !== void 0 || @.default !== void 0) && (@.enum || @.type || @.format || @.$ref || @.properties || @.items))]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.components.schemas..[?(@property !== 'properties' && @ && (@ && @.example !== void 0 || @.default !== void 0) && (@.enum || @.type || @.format || @.$ref || @.properties || @.items))]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..content..[?(@property !== 'properties' && @ && (@ && @.example !== void 0 || @.default !== void 0) && (@.enum || @.type || @.format || @.$ref || @.properties || @.items))]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..headers..[?(@property !== 'properties' && @ && (@ && @.example !== void 0 || @.default !== void 0) && (@.enum || @.type || @.format || @.$ref || @.properties || @.items))]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..parameters..[?(@property !== 'properties' && @ && (@ && @.example !== void 0 || @.default !== void 0) && (@.enum || @.type || @.format || @.$ref || @.properties || @.items))]" - } - ] - }, - { - "id": "eK50ef7RYKpMiaVMvwNrb", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "The complete API specification document. This can be used to target any part of the OpenAPI document using **field**.\n\n*Use this if you don't find specific targets that cater to your usecase.* ", - "name": "API_Document_RecursiveSearch", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.." - }, - { - "formats": [ - "oas3" - ], - "given": "$.." - } - ] - }, - { - "id": "NThdzwnZsKGtbre6bwx6t", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "All examples across the API document", - "name": "All_Example", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..examples[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.components.examples[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.paths[*][*]..content[*].examples[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.paths[*][*]..parameters[*].examples[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.components.parameters[*].examples[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.paths[*][*]..headers[*].examples[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.components.headers[*].examples[*]" - } - ] - }, - { - "id": "HvLVTFsQusOv5EFY2-dsN", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "All examples for schemas", - "name": "All_Example_Media", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..responses..[?(@ && @.schema && @.examples)]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..content..[?(@ && @.schema && (@.example !== void 0 || @.examples))]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..headers..[?(@ && @.schema && (@.example !== void 0 || @.examples))]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..parameters..[?(@ && @.schema && (@.example !== void 0 || @.examples))]" - } - ] - }, - { - "id": "66B1zIgdQwQHlFB83dxpq", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "description": "Tags on an API object", - "name": "API_Tags_Item", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.tags[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.tags[*]" - } - ] - } - ], - "inheritedFunctions": [ - { - "id": "hMJ8u2eQH4QkaEFjA-fnX", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oasDocumentSchema", - "content": "import { createRulesetFunction } from '@stoplight/spectral-core';\nimport { schema as schemaFn } from '@stoplight/spectral-functions';\nimport { oas2, oas3_1 } from '@stoplight/spectral-formats';\n\nconst OAS_2 = {\n title: 'A JSON Schema for Swagger 2.0 API.',\n $id: 'http://swagger.io/v2/schema.json#',\n $schema: 'http://json-schema.org/draft-07/schema#',\n type: 'object',\n required: ['swagger', 'info', 'paths'],\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n swagger: {\n type: 'string',\n enum: ['2.0'],\n description: 'The Swagger version of this document.',\n },\n info: {\n $ref: '#/definitions/info',\n },\n host: {\n type: 'string',\n pattern: '^[^{}/ :\\\\\\\\]+(?::\\\\d+)?$',\n description: \"The host (name or ip) of the API. Example: 'swagger.io'\",\n },\n basePath: {\n type: 'string',\n pattern: '^/',\n description: \"The base path to the API. Example: '/api'.\",\n },\n schemes: {\n $ref: '#/definitions/schemesList',\n },\n consumes: {\n description: 'A list of MIME types accepted by the API.',\n allOf: [\n {\n $ref: '#/definitions/mediaTypeList',\n },\n ],\n },\n produces: {\n description: 'A list of MIME types the API can produce.',\n allOf: [\n {\n $ref: '#/definitions/mediaTypeList',\n },\n ],\n },\n paths: {\n $ref: '#/definitions/paths',\n },\n definitions: {\n $ref: '#/definitions/definitions',\n },\n parameters: {\n $ref: '#/definitions/parameterDefinitions',\n },\n responses: {\n $ref: '#/definitions/responseDefinitions',\n },\n security: {\n $ref: '#/definitions/security',\n },\n securityDefinitions: {\n $ref: '#/definitions/securityDefinitions',\n },\n tags: {\n type: 'array',\n items: {\n $ref: '#/definitions/tag',\n },\n uniqueItems: true,\n },\n externalDocs: {\n $ref: '#/definitions/externalDocs',\n },\n },\n definitions: {\n info: {\n type: 'object',\n description: 'General information about the API.',\n required: ['version', 'title'],\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n title: {\n type: 'string',\n description: 'A unique and precise title of the API.',\n },\n version: {\n type: 'string',\n description: 'A semantic version number of the API.',\n },\n description: {\n type: 'string',\n description:\n 'A longer description of the API. Should be different from the title. GitHub Flavored Markdown is allowed.',\n },\n termsOfService: {\n type: 'string',\n description: 'The terms of service for the API.',\n },\n contact: {\n $ref: '#/definitions/contact',\n },\n license: {\n $ref: '#/definitions/license',\n },\n },\n },\n contact: {\n type: 'object',\n description: 'Contact information for the owners of the API.',\n additionalProperties: false,\n properties: {\n name: {\n type: 'string',\n description: 'The identifying name of the contact person/organization.',\n },\n url: {\n type: 'string',\n description: 'The URL pointing to the contact information.',\n format: 'uri',\n },\n email: {\n type: 'string',\n description: 'The email address of the contact person/organization.',\n format: 'email',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n license: {\n type: 'object',\n required: ['name'],\n additionalProperties: false,\n properties: {\n name: {\n type: 'string',\n description: \"The name of the license type. It's encouraged to use an OSI compatible license.\",\n },\n url: {\n type: 'string',\n description: 'The URL pointing to the license.',\n format: 'uri',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n paths: {\n type: 'object',\n description: \"Relative paths to the individual endpoints. They must be relative to the 'basePath'.\",\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n '^/': {\n $ref: '#/definitions/pathItem',\n },\n },\n additionalProperties: false,\n },\n definitions: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/schema',\n },\n description: 'One or more JSON objects describing the schemas being consumed and produced by the API.',\n },\n parameterDefinitions: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/parameter',\n },\n description: 'One or more JSON representations for parameters',\n },\n responseDefinitions: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/response',\n },\n description: 'One or more JSON representations for responses',\n },\n externalDocs: {\n type: 'object',\n additionalProperties: false,\n description: 'information about external documentation',\n required: ['url'],\n properties: {\n description: {\n type: 'string',\n },\n url: {\n type: 'string',\n format: 'uri',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n examples: {\n type: 'object',\n additionalProperties: true,\n },\n mimeType: {\n type: 'string',\n description: 'The MIME type of the HTTP message.',\n },\n operation: {\n type: 'object',\n required: ['responses'],\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n tags: {\n type: 'array',\n items: {\n type: 'string',\n },\n uniqueItems: true,\n },\n summary: {\n type: 'string',\n description: 'A brief summary of the operation.',\n },\n description: {\n type: 'string',\n description: 'A longer description of the operation, GitHub Flavored Markdown is allowed.',\n },\n externalDocs: {\n $ref: '#/definitions/externalDocs',\n },\n operationId: {\n type: 'string',\n description: 'A unique identifier of the operation.',\n },\n produces: {\n description: 'A list of MIME types the API can produce.',\n allOf: [\n {\n $ref: '#/definitions/mediaTypeList',\n },\n ],\n },\n consumes: {\n description: 'A list of MIME types the API can consume.',\n allOf: [\n {\n $ref: '#/definitions/mediaTypeList',\n },\n ],\n },\n parameters: {\n $ref: '#/definitions/parametersList',\n },\n responses: {\n $ref: '#/definitions/responses',\n },\n schemes: {\n $ref: '#/definitions/schemesList',\n },\n deprecated: {\n type: 'boolean',\n default: false,\n },\n security: {\n $ref: '#/definitions/security',\n },\n },\n },\n pathItem: {\n type: 'object',\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n $ref: {\n type: 'string',\n },\n get: {\n $ref: '#/definitions/operation',\n },\n put: {\n $ref: '#/definitions/operation',\n },\n post: {\n $ref: '#/definitions/operation',\n },\n delete: {\n $ref: '#/definitions/operation',\n },\n options: {\n $ref: '#/definitions/operation',\n },\n head: {\n $ref: '#/definitions/operation',\n },\n patch: {\n $ref: '#/definitions/operation',\n },\n parameters: {\n $ref: '#/definitions/parametersList',\n },\n },\n },\n responses: {\n type: 'object',\n description: \"Response objects names can either be any valid HTTP status code or 'default'.\",\n minProperties: 1,\n additionalProperties: false,\n patternProperties: {\n '^([0-9]{3})$|^(default)$': {\n $ref: '#/definitions/responseValue',\n },\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n not: {\n type: 'object',\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n },\n responseValue: {\n oneOf: [\n {\n $ref: '#/definitions/response',\n },\n {\n $ref: '#/definitions/jsonReference',\n },\n ],\n },\n response: {\n type: 'object',\n required: ['description'],\n properties: {\n description: {\n type: 'string',\n },\n schema: {\n oneOf: [\n {\n $ref: '#/definitions/schema',\n },\n {\n $ref: '#/definitions/fileSchema',\n },\n ],\n },\n headers: {\n $ref: '#/definitions/headers',\n },\n examples: {\n $ref: '#/definitions/examples',\n },\n },\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n headers: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/header',\n },\n },\n header: {\n type: 'object',\n additionalProperties: false,\n required: ['type'],\n properties: {\n type: {\n type: 'string',\n enum: ['string', 'number', 'integer', 'boolean', 'array'],\n },\n format: {\n type: 'string',\n },\n items: {\n $ref: '#/definitions/primitivesItems',\n },\n collectionFormat: {\n $ref: '#/definitions/collectionFormat',\n },\n default: {\n $ref: '#/definitions/default',\n },\n maximum: {\n $ref: '#/definitions/maximum',\n },\n exclusiveMaximum: {\n $ref: '#/definitions/exclusiveMaximum',\n },\n minimum: {\n $ref: '#/definitions/minimum',\n },\n exclusiveMinimum: {\n $ref: '#/definitions/exclusiveMinimum',\n },\n maxLength: {\n $ref: '#/definitions/maxLength',\n },\n minLength: {\n $ref: '#/definitions/minLength',\n },\n pattern: {\n $ref: '#/definitions/pattern',\n },\n maxItems: {\n $ref: '#/definitions/maxItems',\n },\n minItems: {\n $ref: '#/definitions/minItems',\n },\n uniqueItems: {\n $ref: '#/definitions/uniqueItems',\n },\n enum: {\n $ref: '#/definitions/enum',\n },\n multipleOf: {\n $ref: '#/definitions/multipleOf',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n vendorExtension: {\n description: 'Any property starting with x- is valid.',\n additionalProperties: true,\n additionalItems: true,\n },\n bodyParameter: {\n type: 'object',\n required: ['name', 'in', 'schema'],\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n description: {\n type: 'string',\n description:\n 'A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed.',\n },\n name: {\n type: 'string',\n description: 'The name of the parameter.',\n },\n in: {\n type: 'string',\n description: 'Determines the location of the parameter.',\n enum: ['body'],\n },\n required: {\n type: 'boolean',\n description: 'Determines whether or not this parameter is required or optional.',\n default: false,\n },\n schema: {\n $ref: '#/definitions/schema',\n },\n },\n additionalProperties: false,\n },\n headerParameterSubSchema: {\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n required: {\n type: 'boolean',\n description: 'Determines whether or not this parameter is required or optional.',\n default: false,\n },\n in: {\n type: 'string',\n description: 'Determines the location of the parameter.',\n enum: ['header'],\n },\n description: {\n type: 'string',\n description:\n 'A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed.',\n },\n name: {\n type: 'string',\n description: 'The name of the parameter.',\n },\n type: {\n type: 'string',\n enum: ['string', 'number', 'boolean', 'integer', 'array'],\n },\n format: {\n type: 'string',\n },\n items: {\n $ref: '#/definitions/primitivesItems',\n },\n collectionFormat: {\n $ref: '#/definitions/collectionFormat',\n },\n default: {\n $ref: '#/definitions/default',\n },\n maximum: {\n $ref: '#/definitions/maximum',\n },\n exclusiveMaximum: {\n $ref: '#/definitions/exclusiveMaximum',\n },\n minimum: {\n $ref: '#/definitions/minimum',\n },\n exclusiveMinimum: {\n $ref: '#/definitions/exclusiveMinimum',\n },\n maxLength: {\n $ref: '#/definitions/maxLength',\n },\n minLength: {\n $ref: '#/definitions/minLength',\n },\n pattern: {\n $ref: '#/definitions/pattern',\n },\n maxItems: {\n $ref: '#/definitions/maxItems',\n },\n minItems: {\n $ref: '#/definitions/minItems',\n },\n uniqueItems: {\n $ref: '#/definitions/uniqueItems',\n },\n enum: {\n $ref: '#/definitions/enum',\n },\n multipleOf: {\n $ref: '#/definitions/multipleOf',\n },\n },\n },\n queryParameterSubSchema: {\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n required: {\n type: 'boolean',\n description: 'Determines whether or not this parameter is required or optional.',\n default: false,\n },\n in: {\n type: 'string',\n description: 'Determines the location of the parameter.',\n enum: ['query'],\n },\n description: {\n type: 'string',\n description:\n 'A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed.',\n },\n name: {\n type: 'string',\n description: 'The name of the parameter.',\n },\n allowEmptyValue: {\n type: 'boolean',\n default: false,\n description: 'allows sending a parameter by name only or with an empty value.',\n },\n type: {\n type: 'string',\n enum: ['string', 'number', 'boolean', 'integer', 'array'],\n },\n format: {\n type: 'string',\n },\n items: {\n $ref: '#/definitions/primitivesItems',\n },\n collectionFormat: {\n $ref: '#/definitions/collectionFormatWithMulti',\n },\n default: {\n $ref: '#/definitions/default',\n },\n maximum: {\n $ref: '#/definitions/maximum',\n },\n exclusiveMaximum: {\n $ref: '#/definitions/exclusiveMaximum',\n },\n minimum: {\n $ref: '#/definitions/minimum',\n },\n exclusiveMinimum: {\n $ref: '#/definitions/exclusiveMinimum',\n },\n maxLength: {\n $ref: '#/definitions/maxLength',\n },\n minLength: {\n $ref: '#/definitions/minLength',\n },\n pattern: {\n $ref: '#/definitions/pattern',\n },\n maxItems: {\n $ref: '#/definitions/maxItems',\n },\n minItems: {\n $ref: '#/definitions/minItems',\n },\n uniqueItems: {\n $ref: '#/definitions/uniqueItems',\n },\n enum: {\n $ref: '#/definitions/enum',\n },\n multipleOf: {\n $ref: '#/definitions/multipleOf',\n },\n },\n },\n formDataParameterSubSchema: {\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n required: {\n type: 'boolean',\n description: 'Determines whether or not this parameter is required or optional.',\n default: false,\n },\n in: {\n type: 'string',\n description: 'Determines the location of the parameter.',\n enum: ['formData'],\n },\n description: {\n type: 'string',\n description:\n 'A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed.',\n },\n name: {\n type: 'string',\n description: 'The name of the parameter.',\n },\n allowEmptyValue: {\n type: 'boolean',\n default: false,\n description: 'allows sending a parameter by name only or with an empty value.',\n },\n type: {\n type: 'string',\n enum: ['string', 'number', 'boolean', 'integer', 'array', 'file'],\n },\n format: {\n type: 'string',\n },\n items: {\n $ref: '#/definitions/primitivesItems',\n },\n collectionFormat: {\n $ref: '#/definitions/collectionFormatWithMulti',\n },\n default: {\n $ref: '#/definitions/default',\n },\n maximum: {\n $ref: '#/definitions/maximum',\n },\n exclusiveMaximum: {\n $ref: '#/definitions/exclusiveMaximum',\n },\n minimum: {\n $ref: '#/definitions/minimum',\n },\n exclusiveMinimum: {\n $ref: '#/definitions/exclusiveMinimum',\n },\n maxLength: {\n $ref: '#/definitions/maxLength',\n },\n minLength: {\n $ref: '#/definitions/minLength',\n },\n pattern: {\n $ref: '#/definitions/pattern',\n },\n maxItems: {\n $ref: '#/definitions/maxItems',\n },\n minItems: {\n $ref: '#/definitions/minItems',\n },\n uniqueItems: {\n $ref: '#/definitions/uniqueItems',\n },\n enum: {\n $ref: '#/definitions/enum',\n },\n multipleOf: {\n $ref: '#/definitions/multipleOf',\n },\n },\n },\n pathParameterSubSchema: {\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n required: ['required'],\n properties: {\n required: {\n type: 'boolean',\n enum: [true],\n description: 'Determines whether or not this parameter is required or optional.',\n },\n in: {\n type: 'string',\n description: 'Determines the location of the parameter.',\n enum: ['path'],\n },\n description: {\n type: 'string',\n description:\n 'A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed.',\n },\n name: {\n type: 'string',\n description: 'The name of the parameter.',\n },\n type: {\n type: 'string',\n enum: ['string', 'number', 'boolean', 'integer', 'array'],\n },\n format: {\n type: 'string',\n },\n items: {\n $ref: '#/definitions/primitivesItems',\n },\n collectionFormat: {\n $ref: '#/definitions/collectionFormat',\n },\n default: {\n $ref: '#/definitions/default',\n },\n maximum: {\n $ref: '#/definitions/maximum',\n },\n exclusiveMaximum: {\n $ref: '#/definitions/exclusiveMaximum',\n },\n minimum: {\n $ref: '#/definitions/minimum',\n },\n exclusiveMinimum: {\n $ref: '#/definitions/exclusiveMinimum',\n },\n maxLength: {\n $ref: '#/definitions/maxLength',\n },\n minLength: {\n $ref: '#/definitions/minLength',\n },\n pattern: {\n $ref: '#/definitions/pattern',\n },\n maxItems: {\n $ref: '#/definitions/maxItems',\n },\n minItems: {\n $ref: '#/definitions/minItems',\n },\n uniqueItems: {\n $ref: '#/definitions/uniqueItems',\n },\n enum: {\n $ref: '#/definitions/enum',\n },\n multipleOf: {\n $ref: '#/definitions/multipleOf',\n },\n },\n },\n nonBodyParameter: {\n type: 'object',\n required: ['name', 'in', 'type'],\n oneOf: [\n {\n $ref: '#/definitions/headerParameterSubSchema',\n },\n {\n $ref: '#/definitions/formDataParameterSubSchema',\n },\n {\n $ref: '#/definitions/queryParameterSubSchema',\n },\n {\n $ref: '#/definitions/pathParameterSubSchema',\n },\n ],\n },\n parameter: {\n oneOf: [\n {\n $ref: '#/definitions/bodyParameter',\n },\n {\n $ref: '#/definitions/nonBodyParameter',\n },\n ],\n },\n schema: {\n type: 'object',\n description: 'A deterministic version of a JSON Schema object.',\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n $ref: {\n type: 'string',\n },\n format: {\n type: 'string',\n },\n title: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/title',\n },\n description: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/description',\n },\n default: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/default',\n },\n multipleOf: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/multipleOf',\n },\n maximum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/maximum',\n },\n exclusiveMaximum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum',\n },\n minimum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/minimum',\n },\n exclusiveMinimum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum',\n },\n maxLength: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveInteger',\n },\n minLength: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0',\n },\n pattern: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/pattern',\n },\n maxItems: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveInteger',\n },\n minItems: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0',\n },\n uniqueItems: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/uniqueItems',\n },\n maxProperties: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveInteger',\n },\n minProperties: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0',\n },\n required: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/stringArray',\n },\n enum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/enum',\n },\n additionalProperties: {\n anyOf: [\n {\n $ref: '#/definitions/schema',\n },\n {\n type: 'boolean',\n },\n ],\n default: {},\n },\n type: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/type',\n },\n items: {\n anyOf: [\n {\n $ref: '#/definitions/schema',\n },\n {\n type: 'array',\n minItems: 1,\n items: {\n $ref: '#/definitions/schema',\n },\n },\n ],\n default: {},\n },\n allOf: {\n type: 'array',\n minItems: 1,\n items: {\n $ref: '#/definitions/schema',\n },\n },\n oneOf: {\n type: 'array',\n minItems: 1,\n items: {\n $ref: '#/definitions/schema',\n },\n },\n anyOf: {\n type: 'array',\n minItems: 1,\n items: {\n $ref: '#/definitions/schema',\n },\n },\n properties: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/schema',\n },\n default: {},\n },\n discriminator: {\n type: 'string',\n },\n readOnly: {\n type: 'boolean',\n default: false,\n },\n xml: {\n $ref: '#/definitions/xml',\n },\n externalDocs: {\n $ref: '#/definitions/externalDocs',\n },\n example: {},\n },\n additionalProperties: false,\n },\n fileSchema: {\n type: 'object',\n description: 'A deterministic version of a JSON Schema object.',\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n required: ['type'],\n properties: {\n format: {\n type: 'string',\n },\n title: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/title',\n },\n description: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/description',\n },\n default: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/default',\n },\n required: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/stringArray',\n },\n type: {\n type: 'string',\n enum: ['file'],\n },\n readOnly: {\n type: 'boolean',\n default: false,\n },\n externalDocs: {\n $ref: '#/definitions/externalDocs',\n },\n example: {},\n },\n additionalProperties: false,\n },\n primitivesItems: {\n type: 'object',\n additionalProperties: false,\n properties: {\n type: {\n type: 'string',\n enum: ['string', 'number', 'integer', 'boolean', 'array'],\n },\n format: {\n type: 'string',\n },\n items: {\n $ref: '#/definitions/primitivesItems',\n },\n collectionFormat: {\n $ref: '#/definitions/collectionFormat',\n },\n default: {\n $ref: '#/definitions/default',\n },\n maximum: {\n $ref: '#/definitions/maximum',\n },\n exclusiveMaximum: {\n $ref: '#/definitions/exclusiveMaximum',\n },\n minimum: {\n $ref: '#/definitions/minimum',\n },\n exclusiveMinimum: {\n $ref: '#/definitions/exclusiveMinimum',\n },\n maxLength: {\n $ref: '#/definitions/maxLength',\n },\n minLength: {\n $ref: '#/definitions/minLength',\n },\n pattern: {\n $ref: '#/definitions/pattern',\n },\n maxItems: {\n $ref: '#/definitions/maxItems',\n },\n minItems: {\n $ref: '#/definitions/minItems',\n },\n uniqueItems: {\n $ref: '#/definitions/uniqueItems',\n },\n enum: {\n $ref: '#/definitions/enum',\n },\n multipleOf: {\n $ref: '#/definitions/multipleOf',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n security: {\n type: 'array',\n items: {\n $ref: '#/definitions/securityRequirement',\n },\n uniqueItems: true,\n },\n securityRequirement: {\n type: 'object',\n additionalProperties: {\n type: 'array',\n items: {\n type: 'string',\n },\n uniqueItems: true,\n },\n },\n xml: {\n type: 'object',\n additionalProperties: false,\n properties: {\n name: {\n type: 'string',\n },\n namespace: {\n type: 'string',\n },\n prefix: {\n type: 'string',\n },\n attribute: {\n type: 'boolean',\n default: false,\n },\n wrapped: {\n type: 'boolean',\n default: false,\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n tag: {\n type: 'object',\n additionalProperties: false,\n required: ['name'],\n properties: {\n name: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n externalDocs: {\n $ref: '#/definitions/externalDocs',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n securityDefinitions: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/basicAuthenticationSecurity',\n },\n {\n $ref: '#/definitions/apiKeySecurity',\n },\n {\n $ref: '#/definitions/oauth2ImplicitSecurity',\n },\n {\n $ref: '#/definitions/oauth2PasswordSecurity',\n },\n {\n $ref: '#/definitions/oauth2ApplicationSecurity',\n },\n {\n $ref: '#/definitions/oauth2AccessCodeSecurity',\n },\n ],\n },\n errorMessage: {\n properties: {\n basic: 'Invalid basic authentication security definition',\n apiKey: 'Invalid apiKey authentication security definition',\n oauth2: 'Invalid oauth2 authentication security definition',\n },\n _: 'Invalid security securityDefinitions',\n },\n },\n basicAuthenticationSecurity: {\n type: 'object',\n additionalProperties: false,\n required: ['type'],\n properties: {\n type: {\n type: 'string',\n enum: ['basic'],\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n apiKeySecurity: {\n type: 'object',\n additionalProperties: false,\n required: ['type', 'name', 'in'],\n properties: {\n type: {\n type: 'string',\n enum: ['apiKey'],\n },\n name: {\n type: 'string',\n },\n in: {\n type: 'string',\n enum: ['header', 'query'],\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n oauth2ImplicitSecurity: {\n type: 'object',\n additionalProperties: false,\n required: ['type', 'flow', 'authorizationUrl', 'scopes'],\n properties: {\n type: {\n type: 'string',\n enum: ['oauth2'],\n },\n flow: {\n type: 'string',\n enum: ['implicit'],\n },\n scopes: {\n $ref: '#/definitions/oauth2Scopes',\n },\n authorizationUrl: {\n type: 'string',\n format: 'uri',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n oauth2PasswordSecurity: {\n type: 'object',\n additionalProperties: false,\n required: ['type', 'flow', 'tokenUrl', 'scopes'],\n properties: {\n type: {\n type: 'string',\n enum: ['oauth2'],\n },\n flow: {\n type: 'string',\n enum: ['password'],\n },\n scopes: {\n $ref: '#/definitions/oauth2Scopes',\n },\n tokenUrl: {\n type: 'string',\n format: 'uri',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n oauth2ApplicationSecurity: {\n type: 'object',\n additionalProperties: false,\n required: ['type', 'flow', 'tokenUrl', 'scopes'],\n properties: {\n type: {\n type: 'string',\n enum: ['oauth2'],\n },\n flow: {\n type: 'string',\n enum: ['application'],\n },\n scopes: {\n $ref: '#/definitions/oauth2Scopes',\n },\n tokenUrl: {\n type: 'string',\n format: 'uri',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n oauth2AccessCodeSecurity: {\n type: 'object',\n additionalProperties: false,\n required: ['type', 'flow', 'authorizationUrl', 'tokenUrl', 'scopes'],\n properties: {\n type: {\n type: 'string',\n enum: ['oauth2'],\n },\n flow: {\n type: 'string',\n enum: ['accessCode'],\n },\n scopes: {\n $ref: '#/definitions/oauth2Scopes',\n },\n authorizationUrl: {\n type: 'string',\n format: 'uri',\n },\n tokenUrl: {\n type: 'string',\n format: 'uri',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n oauth2Scopes: {\n type: 'object',\n additionalProperties: {\n type: 'string',\n },\n },\n mediaTypeList: {\n type: 'array',\n items: {\n $ref: '#/definitions/mimeType',\n },\n uniqueItems: true,\n },\n parametersList: {\n type: 'array',\n description: 'The parameters needed to send a valid API call.',\n additionalItems: false,\n items: {\n oneOf: [\n {\n $ref: '#/definitions/parameter',\n },\n {\n $ref: '#/definitions/jsonReference',\n },\n ],\n },\n uniqueItems: true,\n },\n schemesList: {\n type: 'array',\n description: 'The transfer protocol of the API.',\n items: {\n type: 'string',\n enum: ['http', 'https', 'ws', 'wss'],\n },\n uniqueItems: true,\n },\n collectionFormat: {\n type: 'string',\n enum: ['csv', 'ssv', 'tsv', 'pipes'],\n default: 'csv',\n },\n collectionFormatWithMulti: {\n type: 'string',\n enum: ['csv', 'ssv', 'tsv', 'pipes', 'multi'],\n default: 'csv',\n },\n title: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/title',\n },\n description: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/description',\n },\n default: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/default',\n },\n multipleOf: {\n type: 'number',\n exclusiveMinimum: 0,\n },\n maximum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/maximum',\n },\n exclusiveMaximum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum',\n },\n minimum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/minimum',\n },\n exclusiveMinimum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum',\n },\n maxLength: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveInteger',\n },\n minLength: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0',\n },\n pattern: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/pattern',\n },\n maxItems: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveInteger',\n },\n minItems: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0',\n },\n uniqueItems: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/uniqueItems',\n },\n enum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/enum',\n },\n jsonReference: {\n type: 'object',\n required: ['$ref'],\n additionalProperties: false,\n properties: {\n $ref: {\n type: 'string',\n },\n },\n },\n },\n};\n\nconst OAS_3 = {\n $id: 'https://spec.openapis.org/oas/3.0/schema/2019-04-02',\n $schema: 'http://json-schema.org/draft-07/schema#',\n description: 'Validation schema for OpenAPI Specification 3.0.X.',\n type: 'object',\n required: ['openapi', 'info', 'paths'],\n properties: {\n openapi: {\n type: 'string',\n pattern: '^3\\\\.0\\\\.\\\\d(-.+)?$',\n },\n info: {\n $ref: '#/definitions/Info',\n },\n externalDocs: {\n $ref: '#/definitions/ExternalDocumentation',\n },\n servers: {\n type: 'array',\n items: {\n $ref: '#/definitions/Server',\n },\n },\n security: {\n type: 'array',\n items: {\n $ref: '#/definitions/SecurityRequirement',\n },\n },\n tags: {\n type: 'array',\n items: {\n $ref: '#/definitions/Tag',\n },\n uniqueItems: true,\n },\n paths: {\n $ref: '#/definitions/Paths',\n },\n components: {\n $ref: '#/definitions/Components',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n definitions: {\n Reference: {\n type: 'object',\n required: ['$ref'],\n patternProperties: {\n '^\\\\$ref$': {\n type: 'string',\n format: 'uri-reference',\n },\n },\n },\n Info: {\n type: 'object',\n required: ['title', 'version'],\n properties: {\n title: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n termsOfService: {\n type: 'string',\n format: 'uri-reference',\n },\n contact: {\n $ref: '#/definitions/Contact',\n },\n license: {\n $ref: '#/definitions/License',\n },\n version: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Contact: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n url: {\n type: 'string',\n format: 'uri-reference',\n },\n email: {\n type: 'string',\n format: 'email',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n License: {\n type: 'object',\n required: ['name'],\n properties: {\n name: {\n type: 'string',\n },\n url: {\n type: 'string',\n format: 'uri-reference',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Server: {\n type: 'object',\n required: ['url'],\n properties: {\n url: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n variables: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/ServerVariable',\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n ServerVariable: {\n type: 'object',\n required: ['default'],\n properties: {\n enum: {\n type: 'array',\n items: {\n type: 'string',\n },\n },\n default: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Components: {\n type: 'object',\n properties: {\n schemas: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n },\n responses: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/Response',\n },\n ],\n },\n },\n },\n parameters: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/Parameter',\n },\n ],\n },\n },\n },\n examples: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/Example',\n },\n ],\n },\n },\n },\n requestBodies: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/RequestBody',\n },\n ],\n },\n },\n },\n headers: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/Header',\n },\n ],\n },\n },\n },\n securitySchemes: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/SecurityScheme',\n },\n ],\n },\n },\n },\n links: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/Link',\n },\n ],\n },\n },\n },\n callbacks: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/Callback',\n },\n ],\n },\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Schema: {\n type: 'object',\n properties: {\n title: {\n type: 'string',\n },\n multipleOf: {\n type: 'number',\n exclusiveMinimum: 0,\n },\n maximum: {\n type: 'number',\n },\n exclusiveMaximum: {\n type: 'boolean',\n default: false,\n },\n minimum: {\n type: 'number',\n },\n exclusiveMinimum: {\n type: 'boolean',\n default: false,\n },\n maxLength: {\n type: 'integer',\n minimum: 0,\n },\n minLength: {\n type: 'integer',\n minimum: 0,\n default: 0,\n },\n pattern: {\n type: 'string',\n format: 'regex',\n },\n maxItems: {\n type: 'integer',\n minimum: 0,\n },\n minItems: {\n type: 'integer',\n minimum: 0,\n default: 0,\n },\n uniqueItems: {\n type: 'boolean',\n default: false,\n },\n maxProperties: {\n type: 'integer',\n minimum: 0,\n },\n minProperties: {\n type: 'integer',\n minimum: 0,\n default: 0,\n },\n required: {\n type: 'array',\n items: {\n type: 'string',\n },\n minItems: 1,\n uniqueItems: true,\n },\n enum: {\n type: 'array',\n items: {},\n minItems: 1,\n uniqueItems: false,\n },\n type: {\n type: 'string',\n enum: ['array', 'boolean', 'integer', 'number', 'object', 'string'],\n },\n not: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n allOf: {\n type: 'array',\n items: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n oneOf: {\n type: 'array',\n items: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n anyOf: {\n type: 'array',\n items: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n items: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n properties: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n {\n type: 'boolean',\n },\n ],\n default: true,\n },\n description: {\n type: 'string',\n },\n format: {\n type: 'string',\n },\n default: {},\n nullable: {\n type: 'boolean',\n default: false,\n },\n discriminator: {\n $ref: '#/definitions/Discriminator',\n },\n readOnly: {\n type: 'boolean',\n default: false,\n },\n writeOnly: {\n type: 'boolean',\n default: false,\n },\n example: {},\n externalDocs: {\n $ref: '#/definitions/ExternalDocumentation',\n },\n deprecated: {\n type: 'boolean',\n default: false,\n },\n xml: {\n $ref: '#/definitions/XML',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Discriminator: {\n type: 'object',\n required: ['propertyName'],\n properties: {\n propertyName: {\n type: 'string',\n },\n mapping: {\n type: 'object',\n additionalProperties: {\n type: 'string',\n },\n },\n },\n },\n XML: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n namespace: {\n type: 'string',\n format: 'uri',\n },\n prefix: {\n type: 'string',\n },\n attribute: {\n type: 'boolean',\n default: false,\n },\n wrapped: {\n type: 'boolean',\n default: false,\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Response: {\n type: 'object',\n required: ['description'],\n properties: {\n description: {\n type: 'string',\n },\n headers: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Header',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n content: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/MediaType',\n },\n },\n links: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Link',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n MediaType: {\n type: 'object',\n properties: {\n schema: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n example: {},\n examples: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Example',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n encoding: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/Encoding',\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n allOf: [\n {\n $ref: '#/definitions/ExampleXORExamples',\n },\n ],\n },\n Example: {\n type: 'object',\n properties: {\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n value: {},\n externalValue: {\n type: 'string',\n format: 'uri-reference',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Header: {\n type: 'object',\n properties: {\n description: {\n type: 'string',\n },\n required: {\n type: 'boolean',\n default: false,\n },\n deprecated: {\n type: 'boolean',\n default: false,\n },\n allowEmptyValue: {\n type: 'boolean',\n default: false,\n },\n style: {\n type: 'string',\n enum: ['simple'],\n default: 'simple',\n },\n explode: {\n type: 'boolean',\n },\n allowReserved: {\n type: 'boolean',\n default: false,\n },\n schema: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n content: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/MediaType',\n },\n minProperties: 1,\n maxProperties: 1,\n },\n example: {},\n examples: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Example',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n allOf: [\n {\n $ref: '#/definitions/ExampleXORExamples',\n },\n {\n $ref: '#/definitions/SchemaXORContent',\n },\n ],\n },\n Paths: {\n type: 'object',\n patternProperties: {\n '^\\\\/': {\n $ref: '#/definitions/PathItem',\n },\n '^x-': {},\n },\n additionalProperties: false,\n },\n PathItem: {\n type: 'object',\n properties: {\n $ref: {\n type: 'string',\n },\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n servers: {\n type: 'array',\n items: {\n $ref: '#/definitions/Server',\n },\n },\n parameters: {\n type: 'array',\n items: {\n oneOf: [\n {\n $ref: '#/definitions/Parameter',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n uniqueItems: true,\n },\n },\n patternProperties: {\n '^(get|put|post|delete|options|head|patch|trace)$': {\n $ref: '#/definitions/Operation',\n },\n '^x-': {},\n },\n additionalProperties: false,\n },\n Operation: {\n type: 'object',\n required: ['responses'],\n properties: {\n tags: {\n type: 'array',\n items: {\n type: 'string',\n },\n },\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n externalDocs: {\n $ref: '#/definitions/ExternalDocumentation',\n },\n operationId: {\n type: 'string',\n },\n parameters: {\n type: 'array',\n items: {\n oneOf: [\n {\n $ref: '#/definitions/Parameter',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n uniqueItems: true,\n },\n requestBody: {\n oneOf: [\n {\n $ref: '#/definitions/RequestBody',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n responses: {\n $ref: '#/definitions/Responses',\n },\n callbacks: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Callback',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n deprecated: {\n type: 'boolean',\n default: false,\n },\n security: {\n type: 'array',\n items: {\n $ref: '#/definitions/SecurityRequirement',\n },\n },\n servers: {\n type: 'array',\n items: {\n $ref: '#/definitions/Server',\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Responses: {\n type: 'object',\n properties: {\n default: {\n oneOf: [\n {\n $ref: '#/definitions/Response',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n patternProperties: {\n '^[1-5](?:\\\\d{2}|XX)$': {\n oneOf: [\n {\n $ref: '#/definitions/Response',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n '^x-': {},\n },\n minProperties: 1,\n additionalProperties: false,\n },\n SecurityRequirement: {\n type: 'object',\n additionalProperties: {\n type: 'array',\n items: {\n type: 'string',\n },\n },\n },\n Tag: {\n type: 'object',\n required: ['name'],\n properties: {\n name: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n externalDocs: {\n $ref: '#/definitions/ExternalDocumentation',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n ExternalDocumentation: {\n type: 'object',\n required: ['url'],\n properties: {\n description: {\n type: 'string',\n },\n url: {\n type: 'string',\n format: 'uri-reference',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n ExampleXORExamples: {\n description: 'Example and examples are mutually exclusive',\n not: {\n required: ['example', 'examples'],\n },\n },\n SchemaXORContent: {\n description: 'Schema and content are mutually exclusive, at least one is required',\n not: {\n required: ['schema', 'content'],\n },\n oneOf: [\n {\n required: ['schema'],\n },\n {\n required: ['content'],\n description: 'Some properties are not allowed if content is present',\n allOf: [\n {\n not: {\n required: ['style'],\n },\n },\n {\n not: {\n required: ['explode'],\n },\n },\n {\n not: {\n required: ['allowReserved'],\n },\n },\n {\n not: {\n required: ['example'],\n },\n },\n {\n not: {\n required: ['examples'],\n },\n },\n ],\n },\n ],\n },\n Parameter: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n in: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n required: {\n type: 'boolean',\n default: false,\n },\n deprecated: {\n type: 'boolean',\n default: false,\n },\n allowEmptyValue: {\n type: 'boolean',\n default: false,\n },\n style: {\n type: 'string',\n },\n explode: {\n type: 'boolean',\n },\n allowReserved: {\n type: 'boolean',\n default: false,\n },\n schema: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n content: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/MediaType',\n },\n minProperties: 1,\n maxProperties: 1,\n },\n example: {},\n examples: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Example',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n required: ['name', 'in'],\n allOf: [\n {\n $ref: '#/definitions/ExampleXORExamples',\n },\n {\n $ref: '#/definitions/SchemaXORContent',\n },\n {\n $ref: '#/definitions/ParameterLocation',\n },\n ],\n },\n ParameterLocation: {\n description: 'Parameter location',\n oneOf: [\n {\n description: 'Parameter in path',\n required: ['required'],\n properties: {\n in: {\n enum: ['path'],\n },\n style: {\n enum: ['matrix', 'label', 'simple'],\n default: 'simple',\n },\n required: {\n enum: [true],\n },\n },\n },\n {\n description: 'Parameter in query',\n properties: {\n in: {\n enum: ['query'],\n },\n style: {\n enum: ['form', 'spaceDelimited', 'pipeDelimited', 'deepObject'],\n default: 'form',\n },\n },\n },\n {\n description: 'Parameter in header',\n properties: {\n in: {\n enum: ['header'],\n },\n style: {\n enum: ['simple'],\n default: 'simple',\n },\n },\n },\n {\n description: 'Parameter in cookie',\n properties: {\n in: {\n enum: ['cookie'],\n },\n style: {\n enum: ['form'],\n default: 'form',\n },\n },\n },\n ],\n },\n RequestBody: {\n type: 'object',\n required: ['content'],\n properties: {\n description: {\n type: 'string',\n },\n content: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/MediaType',\n },\n },\n required: {\n type: 'boolean',\n default: false,\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n SecurityScheme: {\n oneOf: [\n {\n $ref: '#/definitions/APIKeySecurityScheme',\n },\n {\n $ref: '#/definitions/HTTPSecurityScheme',\n },\n {\n $ref: '#/definitions/OAuth2SecurityScheme',\n },\n {\n $ref: '#/definitions/OpenIdConnectSecurityScheme',\n },\n ],\n },\n APIKeySecurityScheme: {\n type: 'object',\n required: ['type', 'name', 'in'],\n properties: {\n type: {\n type: 'string',\n enum: ['apiKey'],\n },\n name: {\n type: 'string',\n },\n in: {\n type: 'string',\n enum: ['header', 'query', 'cookie'],\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n HTTPSecurityScheme: {\n type: 'object',\n required: ['scheme', 'type'],\n properties: {\n scheme: {\n type: 'string',\n },\n bearerFormat: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n type: {\n type: 'string',\n enum: ['http'],\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n oneOf: [\n {\n description: 'Bearer',\n properties: {\n scheme: {\n enum: ['bearer'],\n },\n },\n },\n {\n description: 'Non Bearer',\n not: {\n required: ['bearerFormat'],\n },\n properties: {\n scheme: {\n not: {\n enum: ['bearer'],\n },\n },\n },\n },\n ],\n },\n OAuth2SecurityScheme: {\n type: 'object',\n required: ['type', 'flows'],\n properties: {\n type: {\n type: 'string',\n enum: ['oauth2'],\n },\n flows: {\n $ref: '#/definitions/OAuthFlows',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n OpenIdConnectSecurityScheme: {\n type: 'object',\n required: ['type', 'openIdConnectUrl'],\n properties: {\n type: {\n type: 'string',\n enum: ['openIdConnect'],\n },\n openIdConnectUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n OAuthFlows: {\n type: 'object',\n properties: {\n implicit: {\n $ref: '#/definitions/ImplicitOAuthFlow',\n },\n password: {\n $ref: '#/definitions/PasswordOAuthFlow',\n },\n clientCredentials: {\n $ref: '#/definitions/ClientCredentialsFlow',\n },\n authorizationCode: {\n $ref: '#/definitions/AuthorizationCodeOAuthFlow',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n ImplicitOAuthFlow: {\n type: 'object',\n required: ['authorizationUrl', 'scopes'],\n properties: {\n authorizationUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n refreshUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n scopes: {\n type: 'object',\n additionalProperties: {\n type: 'string',\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n PasswordOAuthFlow: {\n type: 'object',\n required: ['tokenUrl', 'scopes'],\n properties: {\n tokenUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n refreshUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n scopes: {\n type: 'object',\n additionalProperties: {\n type: 'string',\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n ClientCredentialsFlow: {\n type: 'object',\n required: ['tokenUrl', 'scopes'],\n properties: {\n tokenUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n refreshUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n scopes: {\n type: 'object',\n additionalProperties: {\n type: 'string',\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n AuthorizationCodeOAuthFlow: {\n type: 'object',\n required: ['authorizationUrl', 'tokenUrl', 'scopes'],\n properties: {\n authorizationUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n tokenUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n refreshUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n scopes: {\n type: 'object',\n additionalProperties: {\n type: 'string',\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Link: {\n type: 'object',\n properties: {\n operationId: {\n type: 'string',\n },\n operationRef: {\n type: 'string',\n format: 'uri-reference',\n },\n parameters: {\n type: 'object',\n additionalProperties: {},\n },\n requestBody: {},\n description: {\n type: 'string',\n },\n server: {\n $ref: '#/definitions/Server',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n not: {\n description: 'Operation Id and Operation Ref are mutually exclusive',\n required: ['operationId', 'operationRef'],\n },\n },\n Callback: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/PathItem',\n },\n patternProperties: {\n '^x-': {},\n },\n },\n Encoding: {\n type: 'object',\n properties: {\n contentType: {\n type: 'string',\n },\n headers: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/Header',\n },\n },\n style: {\n type: 'string',\n enum: ['form', 'spaceDelimited', 'pipeDelimited', 'deepObject'],\n },\n explode: {\n type: 'boolean',\n },\n allowReserved: {\n type: 'boolean',\n default: false,\n },\n },\n additionalProperties: false,\n },\n },\n};\n\nconst OAS_3_1 = {\n $id: 'https://spec.openapis.org/oas/3.1/schema/2021-09-28',\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n type: 'object',\n properties: {\n openapi: {\n type: 'string',\n pattern: '^3\\\\.1\\\\.\\\\d+(-.+)?$',\n },\n info: {\n $ref: '#/$defs/info',\n },\n jsonSchemaDialect: {\n type: 'string',\n format: 'uri',\n default: 'https://spec.openapis.org/oas/3.1/dialect/base',\n },\n servers: {\n type: 'array',\n items: {\n $ref: '#/$defs/server',\n },\n },\n paths: {\n $ref: '#/$defs/paths',\n },\n webhooks: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/path-item-or-reference',\n },\n },\n components: {\n $ref: '#/$defs/components',\n },\n security: {\n type: 'array',\n items: {\n $ref: '#/$defs/security-requirement',\n },\n },\n tags: {\n type: 'array',\n items: {\n $ref: '#/$defs/tag',\n },\n },\n externalDocs: {\n $ref: '#/$defs/external-documentation',\n },\n },\n required: ['openapi', 'info'],\n anyOf: [\n {\n required: ['paths'],\n errorMessage: 'The document must have either \"paths\", \"webhooks\" or \"components\"',\n },\n {\n required: ['components'],\n },\n {\n required: ['webhooks'],\n },\n ],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n $defs: {\n info: {\n type: 'object',\n properties: {\n title: {\n type: 'string',\n },\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n termsOfService: {\n type: 'string',\n },\n contact: {\n $ref: '#/$defs/contact',\n },\n license: {\n $ref: '#/$defs/license',\n },\n version: {\n type: 'string',\n },\n },\n required: ['title', 'version'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n contact: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n url: {\n type: 'string',\n },\n email: {\n type: 'string',\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n license: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n identifier: {\n type: 'string',\n },\n url: {\n type: 'string',\n format: 'uri',\n },\n },\n required: ['name'],\n oneOf: [\n {\n required: ['identifier'],\n },\n {\n required: ['url'],\n },\n ],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n server: {\n type: 'object',\n properties: {\n url: {\n type: 'string',\n format: 'uri-template',\n },\n description: {\n type: 'string',\n },\n variables: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/server-variable',\n },\n },\n },\n required: ['url'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'server-variable': {\n type: 'object',\n properties: {\n enum: {\n type: 'array',\n items: {\n type: 'string',\n },\n minItems: 1,\n },\n default: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n },\n required: ['default'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n components: {\n type: 'object',\n properties: {\n schemas: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/schema',\n },\n },\n responses: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/response-or-reference',\n },\n },\n parameters: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/parameter-or-reference',\n },\n },\n examples: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/example-or-reference',\n },\n },\n requestBodies: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/request-body-or-reference',\n },\n },\n headers: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/header-or-reference',\n },\n },\n securitySchemes: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/security-scheme-or-reference',\n },\n },\n links: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/link-or-reference',\n },\n },\n callbacks: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/callbacks-or-reference',\n },\n },\n pathItems: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/path-item-or-reference',\n },\n },\n },\n patternProperties: {\n '^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$': {\n $comment:\n 'Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected',\n propertyNames: {\n pattern: '^[a-zA-Z0-9._-]+$',\n },\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n paths: {\n type: 'object',\n patternProperties: {\n '^/': {\n $ref: '#/$defs/path-item',\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'path-item': {\n type: 'object',\n properties: {\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n servers: {\n type: 'array',\n items: {\n $ref: '#/$defs/server',\n },\n },\n parameters: {\n type: 'array',\n items: {\n $ref: '#/$defs/parameter-or-reference',\n },\n },\n },\n patternProperties: {\n '^(get|put|post|delete|options|head|patch|trace)$': {\n $ref: '#/$defs/operation',\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'path-item-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/path-item',\n },\n },\n operation: {\n type: 'object',\n properties: {\n tags: {\n type: 'array',\n items: {\n type: 'string',\n },\n },\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n externalDocs: {\n $ref: '#/$defs/external-documentation',\n },\n operationId: {\n type: 'string',\n },\n parameters: {\n type: 'array',\n items: {\n $ref: '#/$defs/parameter-or-reference',\n },\n },\n requestBody: {\n $ref: '#/$defs/request-body-or-reference',\n },\n responses: {\n $ref: '#/$defs/responses',\n },\n callbacks: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/callbacks-or-reference',\n },\n },\n deprecated: {\n default: false,\n type: 'boolean',\n },\n security: {\n type: 'array',\n items: {\n $ref: '#/$defs/security-requirement',\n },\n },\n servers: {\n type: 'array',\n items: {\n $ref: '#/$defs/server',\n },\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'external-documentation': {\n type: 'object',\n properties: {\n description: {\n type: 'string',\n },\n url: {\n type: 'string',\n format: 'uri',\n },\n },\n required: ['url'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n parameter: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n in: {\n enum: ['query', 'header', 'path', 'cookie'],\n },\n description: {\n type: 'string',\n },\n required: {\n default: false,\n type: 'boolean',\n },\n deprecated: {\n default: false,\n type: 'boolean',\n },\n allowEmptyValue: {\n default: false,\n type: 'boolean',\n },\n schema: {\n $ref: '#/$defs/schema',\n },\n content: {\n $ref: '#/$defs/content',\n },\n },\n required: ['in'],\n oneOf: [\n {\n required: ['schema'],\n },\n {\n required: ['content'],\n },\n ],\n dependentSchemas: {\n schema: {\n properties: {\n style: {\n type: 'string',\n },\n explode: {\n type: 'boolean',\n },\n allowReserved: {\n default: false,\n type: 'boolean',\n },\n },\n allOf: [\n {\n $ref: '#/$defs/examples',\n },\n {\n $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-path',\n },\n {\n $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-header',\n },\n {\n $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-query',\n },\n {\n $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-cookie',\n },\n {\n $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-form',\n },\n ],\n $defs: {\n 'styles-for-path': {\n if: {\n properties: {\n in: {\n const: 'path',\n },\n },\n required: ['in'],\n },\n then: {\n properties: {\n name: {\n pattern: '[^/#?]+$',\n },\n style: {\n default: 'simple',\n enum: ['matrix', 'label', 'simple'],\n },\n required: {\n const: true,\n },\n },\n required: ['required'],\n },\n },\n 'styles-for-header': {\n if: {\n properties: {\n in: {\n const: 'header',\n },\n },\n required: ['in'],\n },\n then: {\n properties: {\n style: {\n default: 'simple',\n const: 'simple',\n },\n },\n },\n },\n 'styles-for-query': {\n if: {\n properties: {\n in: {\n const: 'query',\n },\n },\n required: ['in'],\n },\n then: {\n properties: {\n style: {\n default: 'form',\n enum: ['form', 'spaceDelimited', 'pipeDelimited', 'deepObject'],\n },\n },\n },\n },\n 'styles-for-cookie': {\n if: {\n properties: {\n in: {\n const: 'cookie',\n },\n },\n required: ['in'],\n },\n then: {\n properties: {\n style: {\n default: 'form',\n const: 'form',\n },\n },\n },\n },\n 'styles-for-form': {\n if: {\n properties: {\n style: {\n const: 'form',\n },\n },\n required: ['style'],\n },\n then: {\n properties: {\n explode: {\n default: true,\n },\n },\n },\n else: {\n properties: {\n explode: {\n default: false,\n },\n },\n },\n },\n },\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'parameter-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/parameter',\n },\n },\n 'request-body': {\n type: 'object',\n properties: {\n description: {\n type: 'string',\n },\n content: {\n $ref: '#/$defs/content',\n },\n required: {\n default: false,\n type: 'boolean',\n },\n },\n required: ['content'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'request-body-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/request-body',\n },\n },\n content: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/media-type',\n },\n propertyNames: {\n format: 'media-range',\n },\n },\n 'media-type': {\n type: 'object',\n properties: {\n schema: {\n $ref: '#/$defs/schema',\n },\n encoding: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/encoding',\n },\n },\n },\n allOf: [\n {\n $ref: '#/$defs/specification-extensions',\n },\n {\n $ref: '#/$defs/examples',\n },\n ],\n unevaluatedProperties: false,\n },\n encoding: {\n type: 'object',\n properties: {\n contentType: {\n type: 'string',\n format: 'media-range',\n },\n headers: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/header-or-reference',\n },\n },\n style: {\n default: 'form',\n enum: ['form', 'spaceDelimited', 'pipeDelimited', 'deepObject'],\n },\n explode: {\n type: 'boolean',\n },\n allowReserved: {\n default: false,\n type: 'boolean',\n },\n },\n allOf: [\n {\n $ref: '#/$defs/specification-extensions',\n },\n {\n $ref: '#/$defs/encoding/$defs/explode-default',\n },\n ],\n unevaluatedProperties: false,\n $defs: {\n 'explode-default': {\n if: {\n properties: {\n style: {\n const: 'form',\n },\n },\n required: ['style'],\n },\n then: {\n properties: {\n explode: {\n default: true,\n },\n },\n },\n else: {\n properties: {\n explode: {\n default: false,\n },\n },\n },\n },\n },\n },\n responses: {\n type: 'object',\n properties: {\n default: {\n $ref: '#/$defs/response-or-reference',\n },\n },\n patternProperties: {\n '^[1-5](?:[0-9]{2}|XX)$': {\n $ref: '#/$defs/response-or-reference',\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n response: {\n type: 'object',\n properties: {\n description: {\n type: 'string',\n },\n headers: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/header-or-reference',\n },\n },\n content: {\n $ref: '#/$defs/content',\n },\n links: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/link-or-reference',\n },\n },\n },\n required: ['description'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'response-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/response',\n },\n },\n callbacks: {\n type: 'object',\n $ref: '#/$defs/specification-extensions',\n additionalProperties: {\n $ref: '#/$defs/path-item-or-reference',\n },\n },\n 'callbacks-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/callbacks',\n },\n },\n example: {\n type: 'object',\n properties: {\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n value: true,\n externalValue: {\n type: 'string',\n format: 'uri',\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'example-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/example',\n },\n },\n link: {\n type: 'object',\n properties: {\n operationRef: {\n type: 'string',\n format: 'uri-reference',\n },\n operationId: true,\n parameters: {\n $ref: '#/$defs/map-of-strings',\n },\n requestBody: true,\n description: {\n type: 'string',\n },\n body: {\n $ref: '#/$defs/server',\n },\n },\n oneOf: [\n {\n required: ['operationRef'],\n },\n {\n required: ['operationId'],\n },\n ],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'link-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/link',\n },\n },\n header: {\n type: 'object',\n properties: {\n description: {\n type: 'string',\n },\n required: {\n default: false,\n type: 'boolean',\n },\n deprecated: {\n default: false,\n type: 'boolean',\n },\n schema: {\n $ref: '#/$defs/schema',\n },\n content: {\n $ref: '#/$defs/content',\n },\n },\n oneOf: [\n {\n required: ['schema'],\n },\n {\n required: ['content'],\n },\n ],\n dependentSchemas: {\n schema: {\n properties: {\n style: {\n default: 'simple',\n const: 'simple',\n },\n explode: {\n default: false,\n type: 'boolean',\n },\n },\n $ref: '#/$defs/examples',\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'header-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/header',\n },\n },\n tag: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n externalDocs: {\n $ref: '#/$defs/external-documentation',\n },\n },\n required: ['name'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n reference: {\n type: 'object',\n properties: {\n $ref: {\n type: 'string',\n format: 'uri-reference',\n },\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n },\n unevaluatedProperties: false,\n },\n schema: {\n $dynamicAnchor: 'meta',\n type: ['object', 'boolean'],\n },\n 'security-scheme': {\n type: 'object',\n properties: {\n type: {\n enum: ['apiKey', 'http', 'mutualTLS', 'oauth2', 'openIdConnect'],\n },\n description: {\n type: 'string',\n },\n },\n required: ['type'],\n allOf: [\n {\n $ref: '#/$defs/specification-extensions',\n },\n {\n $ref: '#/$defs/security-scheme/$defs/type-apikey',\n },\n {\n $ref: '#/$defs/security-scheme/$defs/type-http',\n },\n {\n $ref: '#/$defs/security-scheme/$defs/type-http-bearer',\n },\n {\n $ref: '#/$defs/security-scheme/$defs/type-oauth2',\n },\n {\n $ref: '#/$defs/security-scheme/$defs/type-oidc',\n },\n ],\n unevaluatedProperties: false,\n $defs: {\n 'type-apikey': {\n if: {\n properties: {\n type: {\n const: 'apiKey',\n },\n },\n required: ['type'],\n },\n then: {\n properties: {\n name: {\n type: 'string',\n },\n in: {\n enum: ['query', 'header', 'cookie'],\n },\n },\n required: ['name', 'in'],\n },\n },\n 'type-http': {\n if: {\n properties: {\n type: {\n const: 'http',\n },\n },\n required: ['type'],\n },\n then: {\n properties: {\n scheme: {\n type: 'string',\n },\n },\n required: ['scheme'],\n },\n },\n 'type-http-bearer': {\n if: {\n properties: {\n type: {\n const: 'http',\n },\n scheme: {\n type: 'string',\n pattern: '^[Bb][Ee][Aa][Rr][Ee][Rr]$',\n },\n },\n required: ['type', 'scheme'],\n },\n then: {\n properties: {\n bearerFormat: {\n type: 'string',\n },\n },\n },\n },\n 'type-oauth2': {\n if: {\n properties: {\n type: {\n const: 'oauth2',\n },\n },\n required: ['type'],\n },\n then: {\n properties: {\n flows: {\n $ref: '#/$defs/oauth-flows',\n },\n },\n required: ['flows'],\n },\n },\n 'type-oidc': {\n if: {\n properties: {\n type: {\n const: 'openIdConnect',\n },\n },\n required: ['type'],\n },\n then: {\n properties: {\n openIdConnectUrl: {\n type: 'string',\n format: 'uri',\n },\n },\n required: ['openIdConnectUrl'],\n },\n },\n },\n },\n 'security-scheme-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/security-scheme',\n },\n },\n 'oauth-flows': {\n type: 'object',\n properties: {\n implicit: {\n $ref: '#/$defs/oauth-flows/$defs/implicit',\n },\n password: {\n $ref: '#/$defs/oauth-flows/$defs/password',\n },\n clientCredentials: {\n $ref: '#/$defs/oauth-flows/$defs/client-credentials',\n },\n authorizationCode: {\n $ref: '#/$defs/oauth-flows/$defs/authorization-code',\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n $defs: {\n implicit: {\n type: 'object',\n properties: {\n authorizationUrl: {\n type: 'string',\n },\n refreshUrl: {\n type: 'string',\n },\n scopes: {\n $ref: '#/$defs/map-of-strings',\n },\n },\n required: ['authorizationUrl', 'scopes'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n password: {\n type: 'object',\n properties: {\n tokenUrl: {\n type: 'string',\n },\n refreshUrl: {\n type: 'string',\n },\n scopes: {\n $ref: '#/$defs/map-of-strings',\n },\n },\n required: ['tokenUrl', 'scopes'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'client-credentials': {\n type: 'object',\n properties: {\n tokenUrl: {\n type: 'string',\n },\n refreshUrl: {\n type: 'string',\n },\n scopes: {\n $ref: '#/$defs/map-of-strings',\n },\n },\n required: ['tokenUrl', 'scopes'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'authorization-code': {\n type: 'object',\n properties: {\n authorizationUrl: {\n type: 'string',\n },\n tokenUrl: {\n type: 'string',\n },\n refreshUrl: {\n type: 'string',\n },\n scopes: {\n $ref: '#/$defs/map-of-strings',\n },\n },\n required: ['authorizationUrl', 'tokenUrl', 'scopes'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n },\n },\n 'security-requirement': {\n type: 'object',\n additionalProperties: {\n type: 'array',\n items: {\n type: 'string',\n },\n },\n },\n 'specification-extensions': {\n patternProperties: {\n '^x-': true,\n },\n },\n examples: {\n properties: {\n example: true,\n examples: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/example-or-reference',\n },\n },\n },\n },\n 'map-of-strings': {\n type: 'object',\n additionalProperties: {\n type: 'string',\n },\n },\n },\n};\n\nconst OAS_SCHEMAS = {\n '2.0': OAS_2,\n '3.0': OAS_3,\n 3.1: OAS_3_1,\n};\n\nfunction shouldIgnoreError(error) {\n return (\n // oneOf is a fairly error as we have 2 options to choose from for most of the time.\n error.keyword === 'oneOf' ||\n // the required $ref is entirely useless, since oas-schema rules operate on resolved content, so there won't be any $refs in the document\n (error.keyword === 'required' && error.params.missingProperty === '$ref')\n );\n}\n\n// this is supposed to cover edge cases we need to cover manually, when it's impossible to detect the most appropriate error, i.e. oneOf consisting of more than 3 members, etc.\n// note, more errors can be included if certain messages reported by AJV are not quite meaningful\nconst ERROR_MAP = [\n {\n path: /^components\\/securitySchemes\\/[^/]+$/,\n message: 'Invalid security scheme',\n },\n];\n\n// The function removes irrelevant (aka misleading, confusing, useless, whatever you call it) errors.\n// There are a few exceptions, i.e. security components I covered manually,\n// yet apart from them we usually deal with a relatively simple scenario that can be literally expressed as: \"either proper value of $ref property\".\n// The $ref part is never going to be interesting for us, because both oas-schema rules operate on resolved content, so we won't have any $refs left.\n// As you can see, what we deal here wit is actually not really oneOf anymore - it's always the first member of oneOf we match against.\n// That being said, we always strip both oneOf and $ref, since we are always interested in the first error.\nexport function prepareResults(errors) {\n // Update additionalProperties errors to make them more precise and prevent them from being treated as duplicates\n for (const error of errors) {\n if (error.keyword === 'additionalProperties') {\n error.instancePath = `${error.instancePath}/${String(error.params['additionalProperty'])}`;\n }\n }\n\n for (let i = 0; i < errors.length; i++) {\n const error = errors[i];\n\n if (i + 1 < errors.length && errors[i + 1].instancePath === error.instancePath) {\n errors.splice(i + 1, 1);\n i--;\n } else if (i > 0 && shouldIgnoreError(error) && errors[i - 1].instancePath.startsWith(error.instancePath)) {\n errors.splice(i, 1);\n i--;\n }\n }\n}\n\nfunction applyManualReplacements(errors) {\n for (const error of errors) {\n if (error.path === void 0) continue;\n\n const joinedPath = error.path.join('/');\n\n for (const mappedError of ERROR_MAP) {\n if (mappedError.path.test(joinedPath)) {\n error.message = mappedError.message;\n break;\n }\n }\n }\n}\n\nexport default createRulesetFunction(\n {\n input: null,\n options: null,\n },\n function oasDocumentSchema(targetVal, opts, context) {\n const formats = context.document.formats;\n if (formats === null || formats === void 0) return;\n\n const schema = formats.has(oas2)\n ? OAS_SCHEMAS['2.0']\n : formats.has(oas3_1)\n ? OAS_SCHEMAS['3.1']\n : OAS_SCHEMAS['3.0'];\n\n const errors = schemaFn(targetVal, { allErrors: true, schema, prepareResults }, context);\n\n if (Array.isArray(errors)) {\n applyManualReplacements(errors);\n }\n\n return errors;\n },\n);\n" - }, - { - "id": "CJMgNqTpIMItCBSXCw4hZ", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oasDiscriminator", - "content": "function isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nexport const oasDiscriminator = (schema, _opts, { path }) => {\n /**\n * This function verifies:\n *\n * 1. The discriminator property name is defined at this schema.\n * 2. The discriminator property is in the required property list.\n */\n\n if (!isObject(schema)) return;\n\n if (typeof schema.discriminator !== 'string') return;\n\n const discriminatorName = schema.discriminator;\n\n const results = [];\n\n if (!isObject(schema.properties) || !Object.keys(schema.properties).some(k => k === discriminatorName)) {\n results.push({\n message: `The discriminator property must be defined in this schema.`,\n path: [...path, 'properties'],\n });\n }\n\n if (!Array.isArray(schema.required) || !schema.required.some(n => n === discriminatorName)) {\n results.push({\n message: `The discriminator property must be in the required property list.`,\n path: [...path, 'required'],\n });\n }\n\n return results;\n};\n\nexport default oasDiscriminator;\n" - }, - { - "id": "9b-s83e-UQUeeG4bxVKEH", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oasExample", - "content": "import { isPlainObject, pointerToPath } from '@stoplight/json';\nimport { createRulesetFunction } from '@stoplight/spectral-core';\nimport { oas2, oas3_1, extractDraftVersion, oas3_0 } from '@stoplight/spectral-formats';\nimport { schema as schemaFn } from '@stoplight/spectral-functions';\nimport traverse from 'json-schema-traverse';\n\nconst MEDIA_VALIDATION_ITEMS = {\n 2: [\n {\n field: 'examples',\n multiple: true,\n keyed: false,\n },\n ],\n 3: [\n {\n field: 'example',\n multiple: false,\n keyed: false,\n },\n {\n field: 'examples',\n multiple: true,\n keyed: true,\n },\n ],\n};\n\nconst SCHEMA_VALIDATION_ITEMS = {\n 2: ['example', 'x-example', 'default'],\n 3: ['example', 'default'],\n};\n\nfunction isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nfunction rewriteNullable(schema, errors) {\n for (const error of errors) {\n if (error.keyword !== 'type') continue;\n const value = getSchemaProperty(schema, error.schemaPath);\n if (isPlainObject(value) && value.nullable === true) {\n error.message += ',null';\n }\n }\n}\n\nconst visitOAS2 = schema => {\n if (schema['x-nullable'] === true) {\n schema.nullable = true;\n delete schema['x-nullable'];\n }\n};\n\nfunction getSchemaProperty(schema, schemaPath) {\n const path = pointerToPath(schemaPath);\n let value = schema;\n\n for (const fragment of path.slice(0, -1)) {\n if (!isPlainObject(value)) {\n return;\n }\n\n value = value[fragment];\n }\n\n return value;\n}\n\nconst oasSchema = createRulesetFunction(\n {\n input: null,\n options: {\n type: 'object',\n properties: {\n schema: {\n type: 'object',\n },\n },\n additionalProperties: false,\n },\n },\n function oasSchema(targetVal, opts, context) {\n const formats = context.document.formats;\n\n let { schema } = opts;\n\n let dialect = 'draft4';\n let prepareResults;\n\n if (!formats) {\n dialect = 'auto';\n } else if (formats.has(oas3_1)) {\n if (isPlainObject(context.document.data) && typeof context.document.data.jsonSchemaDialect === 'string') {\n dialect = extractDraftVersion(context.document.data.jsonSchemaDialect) ?? 'draft2020-12';\n } else {\n dialect = 'draft2020-12';\n }\n } else if (formats.has(oas3_0)) {\n prepareResults = rewriteNullable.bind(null, schema);\n } else if (formats.has(oas2)) {\n const clonedSchema = JSON.parse(JSON.stringify(schema));\n traverse(clonedSchema, visitOAS2);\n schema = clonedSchema;\n prepareResults = rewriteNullable.bind(null, clonedSchema);\n }\n\n return schemaFn(\n targetVal,\n {\n ...opts,\n schema,\n prepareResults,\n dialect,\n },\n context,\n );\n },\n);\n\nfunction* getMediaValidationItems(items, targetVal, givenPath, oasVersion) {\n for (const { field, keyed, multiple } of items) {\n if (!(field in targetVal)) {\n continue;\n }\n\n const value = targetVal[field];\n\n if (multiple) {\n if (!isObject(value)) continue;\n\n for (const exampleKey of Object.keys(value)) {\n const exampleValue = value[exampleKey];\n if (oasVersion === 3 && keyed && (!isObject(exampleValue) || 'externalValue' in exampleValue)) {\n // should be covered by oas3-examples-value-or-externalValue\n continue;\n }\n\n const targetPath = [...givenPath, field, exampleKey];\n\n if (keyed) {\n targetPath.push('value');\n }\n\n yield {\n value: keyed && isObject(exampleValue) ? exampleValue.value : exampleValue,\n path: targetPath,\n };\n }\n\n return;\n } else {\n return yield {\n value,\n path: [...givenPath, field],\n };\n }\n }\n}\n\nfunction* getSchemaValidationItems(fields, targetVal, givenPath) {\n for (const field of fields) {\n if (!(field in targetVal)) {\n continue;\n }\n\n yield {\n value: targetVal[field],\n path: [...givenPath, field],\n };\n }\n}\n\nexport default createRulesetFunction(\n {\n input: {\n type: 'object',\n },\n options: {\n type: 'object',\n properties: {\n oasVersion: {\n enum: ['2', '3'],\n },\n schemaField: {\n type: 'string',\n },\n type: {\n enum: ['media', 'schema'],\n },\n },\n additionalProperties: false,\n },\n },\n function oasExample(targetVal, opts, context) {\n const formats = context.document.formats;\n const schemaOpts = {\n schema: opts.schemaField === '$' ? targetVal : targetVal[opts.schemaField],\n };\n\n let results = void 0;\n let oasVersion = parseInt(opts.oasVersion);\n\n const validationItems =\n opts.type === 'schema'\n ? getSchemaValidationItems(SCHEMA_VALIDATION_ITEMS[oasVersion], targetVal, context.path)\n : getMediaValidationItems(MEDIA_VALIDATION_ITEMS[oasVersion], targetVal, context.path, oasVersion);\n\n if (formats?.has(oas2) && 'required' in schemaOpts.schema && typeof schemaOpts.schema.required === 'boolean') {\n schemaOpts.schema = { ...schemaOpts.schema };\n delete schemaOpts.schema.required;\n }\n\n for (const validationItem of validationItems) {\n const result = oasSchema(validationItem.value, schemaOpts, {\n ...context,\n path: validationItem.path,\n });\n\n if (Array.isArray(result)) {\n if (results === void 0) results = [];\n results.push(...result);\n }\n }\n\n return results;\n },\n);\n" - }, - { - "id": "cXWUGEh17ZtFUi8V7pS8U", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oasOpFormDataConsumeCheck", - "content": "function isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nconst validConsumeValue = /(application\\/x-www-form-urlencoded|multipart\\/form-data)/;\n\nexport const oasOpFormDataConsumeCheck = targetVal => {\n if (!isObject(targetVal)) return;\n\n const parameters = targetVal.parameters;\n const consumes = targetVal.consumes;\n\n if (!Array.isArray(parameters) || !Array.isArray(consumes)) {\n return;\n }\n\n if (parameters.some(p => isObject(p) && p.in === 'formData') && !validConsumeValue.test(consumes?.join(','))) {\n return [\n {\n message: 'Consumes must include urlencoded, multipart, or form-data media type when using formData parameter.',\n },\n ];\n }\n\n return;\n};\n\nexport default oasOpFormDataConsumeCheck;\n" - }, - { - "id": "ttzuEVCXFKnwpz7tR-rd6", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oasOpIdUnique", - "content": "import { isPlainObject } from '@stoplight/json';\n\nfunction isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nconst validOperationKeys = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace'];\n\nfunction* getAllOperations(paths) {\n if (!isPlainObject(paths)) {\n return;\n }\n\n const item = {\n path: '',\n operation: '',\n value: null,\n };\n\n for (const path of Object.keys(paths)) {\n const operations = paths[path];\n if (!isPlainObject(operations)) {\n continue;\n }\n\n item.path = path;\n\n for (const operation of Object.keys(operations)) {\n if (!isPlainObject(operations[operation]) || !validOperationKeys.includes(operation)) {\n continue;\n }\n\n item.operation = operation;\n item.value = operations[operation];\n\n yield item;\n }\n }\n}\n\nexport const oasOpIdUnique = targetVal => {\n if (!isObject(targetVal) || !isObject(targetVal.paths)) return;\n\n const results = [];\n\n const { paths } = targetVal;\n\n const seenIds = [];\n\n for (const { path, operation } of getAllOperations(paths)) {\n const pathValue = paths[path];\n\n if (!isObject(pathValue)) continue;\n\n const operationValue = pathValue[operation];\n\n if (!isObject(operationValue) || !('operationId' in operationValue)) {\n continue;\n }\n\n const { operationId } = operationValue;\n\n if (seenIds.includes(operationId)) {\n results.push({\n message: 'operationId must be unique.',\n path: ['paths', path, operation, 'operationId'],\n });\n } else {\n seenIds.push(operationId);\n }\n }\n\n return results;\n};\n\nexport default oasOpIdUnique;\n" - }, - { - "id": "iYAdGzBB7ddZ-eP-zLNcg", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oasOpParams", - "content": "function isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nfunction computeFingerprint(param) {\n return `${String(param.in)}-${String(param.name)}`;\n}\n\nexport const oasOpParams = (params, _opts, { path }) => {\n /**\n * This function verifies:\n *\n * 1. Operations must have unique `name` + `in` parameters.\n * 2. Operation cannot have both `in:body` and `in:formData` parameters\n * 3. Operation must have only one `in:body` parameter.\n */\n\n if (!Array.isArray(params)) return;\n\n if (params.length < 2) return;\n\n const results = [];\n\n const count = {\n body: [],\n formData: [],\n };\n const list = [];\n const duplicates = [];\n\n let index = -1;\n\n for (const param of params) {\n index++;\n\n if (!isObject(param)) continue;\n\n // skip params that are refs\n if ('$ref' in param) continue;\n\n // Operations must have unique `name` + `in` parameters.\n const fingerprint = computeFingerprint(param);\n if (list.includes(fingerprint)) {\n duplicates.push(index);\n } else {\n list.push(fingerprint);\n }\n\n if (typeof param.in === 'string' && param.in in count) {\n count[param.in].push(index);\n }\n }\n\n if (duplicates.length > 0) {\n for (const i of duplicates) {\n results.push({\n message: 'A parameter in this operation already exposes the same combination of \"name\" and \"in\" values.',\n path: [...path, i],\n });\n }\n }\n\n if (count.body.length > 0 && count.formData.length > 0) {\n results.push({\n message: 'Operation must not have both \"in:body\" and \"in:formData\" parameters.',\n });\n }\n\n if (count.body.length > 1) {\n for (let i = 1; i < count.body.length; i++) {\n results.push({\n message: 'Operation must not have more than a single instance of the \"in:body\" parameter.',\n path: [...path, count.body[i]],\n });\n }\n }\n\n return results;\n};\n\nexport default oasOpParams;\n" - }, - { - "id": "QxGiBS_ZKR7ebcRcbpE3z", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oasOpSecurityDefined", - "content": "import { isPlainObject } from '@stoplight/json';\nimport { createRulesetFunction } from '@stoplight/spectral-core';\n\nfunction isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nconst validOperationKeys = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace'];\n\nfunction* getAllOperations(paths) {\n if (!isPlainObject(paths)) {\n return;\n }\n\n const item = {\n path: '',\n operation: '',\n value: null,\n };\n\n for (const path of Object.keys(paths)) {\n const operations = paths[path];\n if (!isPlainObject(operations)) {\n continue;\n }\n\n item.path = path;\n\n for (const operation of Object.keys(operations)) {\n if (!isPlainObject(operations[operation]) || !validOperationKeys.includes(operation)) {\n continue;\n }\n\n item.operation = operation;\n item.value = operations[operation];\n\n yield item;\n }\n }\n}\n\nfunction _get(value, path) {\n for (const segment of path) {\n if (!isObject(value)) {\n break;\n }\n\n value = value[segment];\n }\n\n return value;\n}\n\nexport default createRulesetFunction(\n {\n input: {\n type: 'object',\n properties: {\n paths: {\n type: 'object',\n },\n security: {\n type: 'array',\n },\n },\n },\n options: {\n type: 'object',\n properties: {\n schemesPath: {\n type: 'array',\n items: {\n type: ['string', 'number'],\n },\n },\n },\n },\n },\n function oasOpSecurityDefined(targetVal, { schemesPath }) {\n const { paths } = targetVal;\n\n const results = [];\n\n const schemes = _get(targetVal, schemesPath);\n const allDefs = isObject(schemes) ? Object.keys(schemes) : [];\n\n // Check global security requirements\n\n const { security } = targetVal;\n\n if (Array.isArray(security)) {\n for (const [index, value] of security.entries()) {\n if (!isObject(value)) {\n continue;\n }\n\n const securityKeys = Object.keys(value);\n\n for (const securityKey of securityKeys) {\n if (!allDefs.includes(securityKey)) {\n results.push({\n message: `API \"security\" values must match a scheme defined in the \"${schemesPath.join('.')}\" object.`,\n path: ['security', index, securityKey],\n });\n }\n }\n }\n }\n\n for (const { path, operation, value } of getAllOperations(paths)) {\n if (!isObject(value)) continue;\n\n const { security } = value;\n\n if (!Array.isArray(security)) {\n continue;\n }\n\n for (const [index, value] of security.entries()) {\n if (!isObject(value)) {\n continue;\n }\n\n const securityKeys = Object.keys(value);\n\n for (const securityKey of securityKeys) {\n if (!allDefs.includes(securityKey)) {\n results.push({\n message: `Operation \"security\" values must match a scheme defined in the \"${schemesPath.join(\n '.',\n )}\" object.`,\n path: ['paths', path, operation, 'security', index, securityKey],\n });\n }\n }\n }\n }\n\n return results;\n },\n);\n" - }, - { - "id": "7TIetcBL2y78cjhvupnPo", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oasOpSuccessResponse", - "content": "import { createRulesetFunction } from '@stoplight/spectral-core';\nimport { oas3 } from '@stoplight/spectral-formats';\n\nexport const oasOpSuccessResponse = createRulesetFunction(\n {\n input: {\n type: 'object',\n },\n options: null,\n },\n (input, opts, context) => {\n const isOAS3X = context.document.formats?.has(oas3) === true;\n\n for (const response of Object.keys(input)) {\n if (isOAS3X && (response === '2XX' || response === '3XX')) {\n return;\n }\n\n if (Number(response) >= 200 && Number(response) < 400) {\n return;\n }\n }\n\n return [\n {\n message: 'Operation must define at least a single 2xx or 3xx response',\n },\n ];\n },\n);\n\nexport default oasOpSuccessResponse;\n" - }, - { - "id": "YgFRN8F0T3qVoo6s2lEu-", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oasPathParam", - "content": "function isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nconst pathRegex = /(\\{;?\\??[a-zA-Z0-9_-]+\\*?\\})/g;\n\nconst isNamedPathParam = p => {\n return p.in !== void 0 && p.in === 'path' && p.name !== void 0;\n};\n\nconst isUnknownNamedPathParam = (p, path, results, seen) => {\n if (!isNamedPathParam(p)) {\n return false;\n }\n\n if (p.required !== true) {\n results.push(generateResult(requiredMessage(p.name), path));\n }\n\n if (p.name in seen) {\n results.push(generateResult(uniqueDefinitionMessage(p.name), path));\n return false;\n }\n\n return true;\n};\n\nconst ensureAllDefinedPathParamsAreUsedInPath = (path, params, expected, results) => {\n for (const p of Object.keys(params)) {\n if (!params[p]) {\n continue;\n }\n\n if (!expected.includes(p)) {\n const resPath = params[p];\n results.push(generateResult(`Parameter \"${p}\" must be used in path \"${path}\".`, resPath));\n }\n }\n};\n\nconst ensureAllExpectedParamsInPathAreDefined = (path, params, expected, operationPath, results) => {\n for (const p of expected) {\n if (!(p in params)) {\n results.push(\n generateResult(`Operation must define parameter \"{${p}}\" as expected by path \"${path}\".`, operationPath),\n );\n }\n }\n};\n\nexport const oasPathParam = targetVal => {\n /**\n * This rule verifies:\n *\n * 1. for every param referenced in the path string ie /users/{userId}, var must be defined in either\n * path.parameters, or operation.parameters object\n * 2. every path.parameters + operation.parameters property must be used in the path string\n */\n\n if (!isObject(targetVal) || !isObject(targetVal.paths)) {\n return;\n }\n\n const results = [];\n\n // keep track of normalized paths for verifying paths are unique\n const uniquePaths = {};\n const validOperationKeys = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace'];\n\n for (const path of Object.keys(targetVal.paths)) {\n const pathValue = targetVal.paths[path];\n if (!isObject(pathValue)) continue;\n\n // verify normalized paths are functionally unique (ie `/path/{one}` vs `/path/{two}` are\n // different but equivalent within the context of OAS)\n const normalized = path.replace(pathRegex, '%'); // '%' is used here since its invalid in paths\n if (normalized in uniquePaths) {\n results.push(\n generateResult(`Paths \"${String(uniquePaths[normalized])}\" and \"${path}\" must not be equivalent.`, [\n 'paths',\n path,\n ]),\n );\n } else {\n uniquePaths[normalized] = path;\n }\n\n // find all templated path parameters\n const pathElements = [];\n let match;\n\n while ((match = pathRegex.exec(path))) {\n const p = match[0].replace(/[{}?*;]/g, '');\n if (pathElements.includes(p)) {\n results.push(generateResult(`Path \"${path}\" must not use parameter \"{${p}}\" multiple times.`, ['paths', path]));\n } else {\n pathElements.push(p);\n }\n }\n\n // find parameters set within the top-level 'parameters' object\n const topParams = {};\n if (Array.isArray(pathValue.parameters)) {\n for (const [i, value] of pathValue.parameters.entries()) {\n if (!isObject(value)) continue;\n\n const fullParameterPath = ['paths', path, 'parameters', i];\n\n if (isUnknownNamedPathParam(value, fullParameterPath, results, topParams)) {\n topParams[value.name] = fullParameterPath;\n }\n }\n }\n\n if (isObject(targetVal.paths[path])) {\n // find parameters set within the operation's 'parameters' object\n for (const op of Object.keys(pathValue)) {\n const operationValue = pathValue[op];\n if (!isObject(operationValue)) continue;\n\n if (op === 'parameters' || !validOperationKeys.includes(op)) {\n continue;\n }\n\n const operationParams = {};\n const { parameters } = operationValue;\n const operationPath = ['paths', path, op];\n\n if (Array.isArray(parameters)) {\n for (const [i, p] of parameters.entries()) {\n if (!isObject(p)) continue;\n\n const fullParameterPath = [...operationPath, 'parameters', i];\n\n if (isUnknownNamedPathParam(p, fullParameterPath, results, operationParams)) {\n operationParams[p.name] = fullParameterPath;\n }\n }\n }\n\n const definedParams = { ...topParams, ...operationParams };\n ensureAllDefinedPathParamsAreUsedInPath(path, definedParams, pathElements, results);\n ensureAllExpectedParamsInPathAreDefined(path, definedParams, pathElements, operationPath, results);\n }\n }\n }\n\n return results;\n};\n\nfunction generateResult(message, path) {\n return {\n message,\n path,\n };\n}\n\nconst requiredMessage = name => `Path parameter \"${name}\" must have \"required\" property that is set to \"true\".`;\n\nconst uniqueDefinitionMessage = name => `Path parameter \"${name}\" must not be defined multiple times.`;\n\nexport default oasPathParam;\n" - }, - { - "id": "iu5I3elrGXYkaMYm4ba8A", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oasSchema", - "content": "import traverse from 'json-schema-traverse';\nimport { schema as schemaFn } from '@stoplight/spectral-functions';\nimport { createRulesetFunction } from '@stoplight/spectral-core';\nimport { oas2, oas3_1, extractDraftVersion, oas3_0 } from '@stoplight/spectral-formats';\nimport { isPlainObject, pointerToPath } from '@stoplight/json';\n\nfunction rewriteNullable(schema, errors) {\n for (const error of errors) {\n if (error.keyword !== 'type') continue;\n const value = getSchemaProperty(schema, error.schemaPath);\n if (isPlainObject(value) && value.nullable === true) {\n error.message += ',null';\n }\n }\n}\n\nexport default createRulesetFunction(\n {\n input: null,\n options: {\n type: 'object',\n properties: {\n schema: {\n type: 'object',\n },\n },\n additionalProperties: false,\n },\n },\n function oasSchema(targetVal, opts, context) {\n const formats = context.document.formats;\n\n let { schema } = opts;\n\n let dialect = 'draft4';\n let prepareResults;\n\n if (!formats) {\n dialect = 'auto';\n } else if (formats.has(oas3_1)) {\n if (isPlainObject(context.document.data) && typeof context.document.data.jsonSchemaDialect === 'string') {\n dialect = extractDraftVersion(context.document.data.jsonSchemaDialect) ?? 'draft2020-12';\n } else {\n dialect = 'draft2020-12';\n }\n } else if (formats.has(oas3_0)) {\n prepareResults = rewriteNullable.bind(null, schema);\n } else if (formats.has(oas2)) {\n const clonedSchema = JSON.parse(JSON.stringify(schema));\n traverse(clonedSchema, visitOAS2);\n schema = clonedSchema;\n prepareResults = rewriteNullable.bind(null, clonedSchema);\n }\n\n return schemaFn(\n targetVal,\n {\n ...opts,\n schema,\n prepareResults,\n dialect,\n },\n context,\n );\n },\n);\n\nconst visitOAS2 = schema => {\n if (schema['x-nullable'] === true) {\n schema.nullable = true;\n delete schema['x-nullable'];\n }\n};\n\nfunction getSchemaProperty(schema, schemaPath) {\n const path = pointerToPath(schemaPath);\n let value = schema;\n\n for (const fragment of path.slice(0, -1)) {\n if (!isPlainObject(value)) {\n return;\n }\n\n value = value[fragment];\n }\n\n return value;\n}\n" - }, - { - "id": "aMJLIDcooVXid-N3unFUF", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oasTagDefined", - "content": "// This function will check an API doc to verify that any tag that appears on\n// an operation is also present in the global tags array.\nimport { isPlainObject } from '@stoplight/json';\n\nfunction isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nconst validOperationKeys = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace'];\n\nfunction* getAllOperations(paths) {\n if (!isPlainObject(paths)) {\n return;\n }\n\n const item = {\n path: '',\n operation: '',\n value: null,\n };\n\n for (const path of Object.keys(paths)) {\n const operations = paths[path];\n if (!isPlainObject(operations)) {\n continue;\n }\n\n item.path = path;\n\n for (const operation of Object.keys(operations)) {\n if (!isPlainObject(operations[operation]) || !validOperationKeys.includes(operation)) {\n continue;\n }\n\n item.operation = operation;\n item.value = operations[operation];\n\n yield item;\n }\n }\n}\n\nexport const oasTagDefined = targetVal => {\n if (!isObject(targetVal)) return;\n const results = [];\n\n const globalTags = [];\n\n if (Array.isArray(targetVal.tags)) {\n for (const tag of targetVal.tags) {\n if (isObject(tag) && typeof tag.name === 'string') {\n globalTags.push(tag.name);\n }\n }\n }\n\n const { paths } = targetVal;\n\n for (const { path, operation, value } of getAllOperations(paths)) {\n if (!isObject(value)) continue;\n\n const { tags } = value;\n\n if (!Array.isArray(tags)) {\n continue;\n }\n\n for (const [i, tag] of tags.entries()) {\n if (!globalTags.includes(tag)) {\n results.push({\n message: 'Operation tags must be defined in global tags.',\n path: ['paths', path, operation, 'tags', i],\n });\n }\n }\n }\n\n return results;\n};\n\nexport default oasTagDefined;\n" - }, - { - "id": "Xcd_EpoSBxmDhMrljL_L7", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "oasUnusedComponent", - "content": "import { unreferencedReusableObject } from '@stoplight/spectral-functions';\nimport { createRulesetFunction } from '@stoplight/spectral-core';\n\nfunction isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nexport default createRulesetFunction(\n {\n input: {\n type: 'object',\n properties: {\n components: {\n type: 'object',\n },\n },\n required: ['components'],\n },\n options: null,\n },\n function oasUnusedComponent(targetVal, opts, context) {\n const results = [];\n const componentTypes = [\n 'schemas',\n 'responses',\n 'parameters',\n 'examples',\n 'requestBodies',\n 'headers',\n 'links',\n 'callbacks',\n ];\n\n for (const type of componentTypes) {\n const value = targetVal.components[type];\n if (!isObject(value)) continue;\n\n const resultsForType = unreferencedReusableObject(\n value,\n { reusableObjectsLocation: `#/components/${type}` },\n context,\n );\n if (resultsForType !== void 0 && Array.isArray(resultsForType)) {\n results.push(...resultsForType);\n }\n }\n\n return results;\n },\n);\n" - }, - { - "id": "gTwx-__B_79Idu-aRnXov", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "refSiblings", - "content": "function isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nfunction getParentValue(document, path) {\n if (path.length === 0) {\n return null;\n }\n\n let piece = document;\n\n for (let i = 0; i < path.length - 1; i += 1) {\n if (!isObject(piece)) {\n return null;\n }\n\n piece = piece[path[i]];\n }\n\n return piece;\n}\n\nconst refSiblings = (targetVal, opts, { document, path }) => {\n const value = getParentValue(document.data, path);\n\n if (!isObject(value)) {\n return;\n }\n\n const keys = Object.keys(value);\n if (keys.length === 1) {\n return;\n }\n\n const results = [];\n const actualObjPath = path.slice(0, -1);\n\n for (const key of keys) {\n if (key === '$ref') {\n continue;\n }\n results.push({\n message: '$ref must not be placed next to any other properties',\n path: [...actualObjPath, key],\n });\n }\n\n return results;\n};\n\nexport default refSiblings;\n" - }, - { - "id": "F8fU5xMpee5evb5azXzHw", - "extendedFrom": "cHJqOjEyMzU4Ng@32", - "name": "typedEnum", - "content": "import { oas2, oas3_0 } from '@stoplight/spectral-formats';\nimport { printValue } from '@stoplight/spectral-runtime';\nimport { createRulesetFunction } from '@stoplight/spectral-core';\n\nfunction getDataType(input, checkForInteger) {\n const type = typeof input;\n switch (type) {\n case 'string':\n case 'boolean':\n return type;\n case 'number':\n if (checkForInteger && Number.isInteger(input)) {\n return 'integer';\n }\n\n return 'number';\n case 'object':\n if (input === null) {\n return 'null';\n }\n\n return Array.isArray(input) ? 'array' : 'object';\n default:\n throw TypeError('Unknown input type');\n }\n}\n\nfunction getTypes(input, formats) {\n const { type } = input;\n\n if (\n (input.nullable === true && formats?.has(oas3_0) === true) ||\n (input['x-nullable'] === true && formats?.has(oas2) === true)\n ) {\n return Array.isArray(type) ? [...type, 'null'] : [type, 'null'];\n }\n\n return type;\n}\n\nexport const typedEnum = createRulesetFunction(\n {\n input: {\n type: 'object',\n properties: {\n enum: {\n type: 'array',\n },\n type: {\n oneOf: [\n {\n type: 'array',\n items: {\n type: 'string',\n },\n },\n {\n type: 'string',\n },\n ],\n },\n },\n required: ['enum', 'type'],\n },\n options: null,\n },\n function (input, opts, context) {\n const { enum: enumValues } = input;\n const type = getTypes(input, context.document.formats);\n const checkForInteger = type === 'integer' || (Array.isArray(type) && type.includes('integer'));\n\n let results;\n\n enumValues.forEach((value, i) => {\n const valueType = getDataType(value, checkForInteger);\n\n if (valueType === type || (Array.isArray(type) && type.includes(valueType))) {\n return;\n }\n\n results ??= [];\n results.push({\n message: `Enum value ${printValue(enumValues[i])} must be \"${String(type)}\".`,\n path: [...context.path, 'enum', i],\n });\n });\n\n return results;\n },\n);\n\nexport default typedEnum;\n" - } - ], - "library": { - "cHJqOjEyMzU4Ng@32": { - "description": "This is Stoplight's built-in style guide, containing style and validation rules for OAS (OpenAPI Specification).\n\n> This Style Guide is applied to all new projects by default.", - "formats": [ - "oas2", - "oas3", - "oas3.0", - "oas3.1" - ], - "aliases": { - "API_Document": { - "description": "The complete API specification document. This can be used to target any part of the OpenAPI document using **field**.\n\n*Use this if you don't find specific targets that cater to your usecase.* ", - "name": "API_Document", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$" - }, - { - "formats": [ - "oas3" - ], - "given": "$" - } - ] - }, - "API_Description": { - "description": "The top level description in an API document", - "name": "API_Description", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.info.description" - }, - { - "formats": [ - "oas3" - ], - "given": "$.info.description" - } - ] - }, - "Operation_Object": { - "description": "The complete operation object. Use it in combo with field object.", - "name": "Operation_Object", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "#Path_Item[get,put,post,delete,options,head,patch,trace]" - }, - { - "formats": [ - "oas3" - ], - "given": "#Path_Item[get,put,post,delete,options,head,patch,trace]" - } - ] - }, - "Operation_Responses": { - "description": "Responses for all operations including get, put, post, delete, options, head, patch, trace.", - "name": "Operation_Responses", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "#Operation_Object.responses" - }, - { - "formats": [ - "oas3" - ], - "given": "#Operation_Object.responses" - } - ] - }, - "Path_Item": { - "name": "Path_Item", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.paths[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.paths[*]" - } - ] - }, - "API_Contact": { - "description": "The top level description in an API document", - "name": "API_Contact", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.info.contact" - }, - { - "formats": [ - "oas3" - ], - "given": "$.info.contact" - } - ] - }, - "API_License": { - "description": "The top level description in an API document", - "name": "API_License", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.info.license" - }, - { - "formats": [ - "oas3" - ], - "given": "$.info.license" - } - ] - }, - "All_Markdown": { - "description": "All markdown descriptions across the document.", - "name": "All_Markdown", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..[description,title]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..[description,title]" - } - ] - }, - "API_Tags": { - "description": "Tags on an API object", - "name": "API_Tags", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.tags" - }, - { - "formats": [ - "oas3" - ], - "given": "$.tags" - } - ] - }, - "API_Server": { - "description": "API hosts defined in the API specification", - "name": "API_Server", - "targets": [ - { - "formats": [ - "oas3" - ], - "given": "$.servers" - }, - { - "formats": [ - "oas2" - ], - "given": "$.host" - } - ] - }, - "Response_All_Object": { - "description": "All responses (object) in an API", - "name": "Response_All_Object", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.responses" - }, - { - "formats": [ - "oas2" - ], - "given": "#Operation_Responses" - }, - { - "formats": [ - "oas3" - ], - "given": "$.components.responses" - }, - { - "formats": [ - "oas3" - ], - "given": "#Operation_Responses" - }, - { - "formats": [ - "oas3" - ], - "given": "$..responses" - }, - { - "formats": [ - "oas2" - ], - "given": "$..responses" - } - ] - }, - "API_Server_URL": { - "description": "API host urls defined in the API specification", - "name": "API_Server_URL", - "targets": [ - { - "formats": [ - "oas3" - ], - "given": "$.servers[*].url" - }, - { - "formats": [ - "oas2" - ], - "given": "$.host" - } - ] - }, - "All_Ref": { - "description": "All references throughout the API", - "name": "All_Ref", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..[?(@property === '$ref')]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..[?(@property === '$ref')]" - } - ] - }, - "All_Enum": { - "description": "All references throughout the API", - "name": "All_Enum", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..[?(@ && @.enum && @.type)]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..[?(@ && @.enum && @.type)]" - } - ] - }, - "Request_Parameter_All": { - "description": "All request parameters", - "name": "Request_Parameter_All", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..parameters[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..parameters[*]" - } - ] - }, - "Request_Parameter_Query": { - "description": "All request query parameters", - "name": "Request_Parameter_Query", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..parameters[?(@.in==\"query\")]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..parameters[?(@.in==\"query\")]" - } - ] - }, - "Request_Parameter_Header": { - "description": "All request header parameters", - "name": "Request_Parameter_Header", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..parameters[?(@.in==\"header\")]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..parameters[?(@.in==\"header\")]" - } - ] - }, - "Request_Parameter_Cookie": { - "description": "All request cookie parameters", - "name": "Request_Parameter_Cookie", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..parameters[?(@.in==\"cookie\")]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..parameters[?(@.in==\"cookie\")]" - } - ] - }, - "Request_Parameter_Path": { - "description": "All request path parameters", - "name": "Request_Parameter_Path", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..parameters[?(@.in==\"path\")]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..parameters[?(@.in==\"path\")]" - } - ] - }, - "Path_Object": { - "description": "Path object. Usually used to target the Path key e.g. `/users/{userId}`", - "name": "Path_Object", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.paths" - }, - { - "formats": [ - "oas3" - ], - "given": "$.paths" - } - ] - }, - "All_Example_Schema": { - "description": "All examples for schemas", - "name": "All_Example_Schema", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..definitions..[?(@property !== 'properties' && @ && (@.example !== void 0 || @['x-example'] !== void 0 || @.default !== void 0) && (@.enum || @.type || @.format || @.$ref || @.properties || @.items))]" - }, - { - "formats": [ - "oas2" - ], - "given": "$..parameters..[?(@property !== 'properties' && @ && (@.example !== void 0 || @['x-example'] !== void 0 || @.default !== void 0) && (@.enum || @.type || @.format || @.$ref || @.properties || @.items))]" - }, - { - "formats": [ - "oas2" - ], - "given": "$..responses..[?(@property !== 'properties' && @ && (@.example !== void 0 || @['x-example'] !== void 0 || @.default !== void 0) && (@.enum || @.type || @.format || @.$ref || @.properties || @.items))]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.components.schemas..[?(@property !== 'properties' && @ && (@ && @.example !== void 0 || @.default !== void 0) && (@.enum || @.type || @.format || @.$ref || @.properties || @.items))]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..content..[?(@property !== 'properties' && @ && (@ && @.example !== void 0 || @.default !== void 0) && (@.enum || @.type || @.format || @.$ref || @.properties || @.items))]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..headers..[?(@property !== 'properties' && @ && (@ && @.example !== void 0 || @.default !== void 0) && (@.enum || @.type || @.format || @.$ref || @.properties || @.items))]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..parameters..[?(@property !== 'properties' && @ && (@ && @.example !== void 0 || @.default !== void 0) && (@.enum || @.type || @.format || @.$ref || @.properties || @.items))]" - } - ] - }, - "API_Document_RecursiveSearch": { - "description": "The complete API specification document. This can be used to target any part of the OpenAPI document using **field**.\n\n*Use this if you don't find specific targets that cater to your usecase.* ", - "name": "API_Document_RecursiveSearch", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.." - }, - { - "formats": [ - "oas3" - ], - "given": "$.." - } - ] - }, - "All_Example": { - "description": "All examples across the API document", - "name": "All_Example", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..examples[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.components.examples[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.paths[*][*]..content[*].examples[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.paths[*][*]..parameters[*].examples[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.components.parameters[*].examples[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.paths[*][*]..headers[*].examples[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.components.headers[*].examples[*]" - } - ] - }, - "All_Example_Media": { - "description": "All examples for schemas", - "name": "All_Example_Media", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$..responses..[?(@ && @.schema && @.examples)]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..content..[?(@ && @.schema && (@.example !== void 0 || @.examples))]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..headers..[?(@ && @.schema && (@.example !== void 0 || @.examples))]" - }, - { - "formats": [ - "oas3" - ], - "given": "$..parameters..[?(@ && @.schema && (@.example !== void 0 || @.examples))]" - } - ] - }, - "API_Tags_Item": { - "description": "Tags on an API object", - "name": "API_Tags_Item", - "targets": [ - { - "formats": [ - "oas2" - ], - "given": "$.tags[*]" - }, - { - "formats": [ - "oas3" - ], - "given": "$.tags[*]" - } - ] - } - }, - "rules": { - "contact-url": { - "given": [ - "#API_Contact" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "url" - }, - "name": "contact-url", - "description": "Contact object should have \"url\"", - "message": "Contact object should have \"url\"." - }, - "contact-email": { - "given": [ - "#API_Contact" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "email" - }, - "name": "contact-email", - "description": "Contact object should have \"email\"", - "message": "Contact object should have \"email\"" - }, - "info-contact": { - "given": [ - "#API_Document" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "info.contact" - }, - "name": "info-contact", - "description": "Info object should have \"contact\" object.", - "message": "Info object should have \"contact\" object." - }, - "info-description": { - "given": [ - "#API_Document" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "info.description" - }, - "name": "info-description", - "description": "Info object should have \"description\" object.", - "message": "Info object should have \"description\" object." - }, - "info-license": { - "given": [ - "#API_Document" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "info.license" - }, - "name": "info-license", - "description": "Info object should have \"license\" object.", - "message": "Info object should have \"license\" object." - }, - "license-url": { - "given": [ - "#API_License" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "url" - }, - "name": "license-url", - "description": "License object should include \"url\".", - "message": "License object should include \"url\"." - }, - "no-eval-in-markdown": { - "given": [ - "#All_Markdown" - ], - "severity": "warn", - "then": { - "function": "pattern", - "functionOptions": { - "notMatch": "eval\\(" - } - }, - "name": "no-eval-in-markdown", - "description": "Markdown descriptions must not have \"eval(\".", - "message": "Markdown descriptions must not have \"eval(\"." - }, - "no-script-tags-in-markdown": { - "given": [ - "#All_Markdown" - ], - "severity": "warn", - "then": { - "function": "pattern", - "functionOptions": { - "notMatch": "\" tags." - }, - "openapi-tags-alphabetical": { - "given": [ - "#API_Tags" - ], - "severity": "warn", - "then": { - "function": "alphabetical", - "functionOptions": { - "keyedBy": "name" - } - }, - "name": "openapi-tags-alphabetical", - "description": "OpenAPI object should have alphabetical \"tags\".", - "message": "OpenAPI object should have alphabetical \"tags\"." - }, - "openapi-tags": { - "given": [ - "#API_Tags" - ], - "severity": "warn", - "then": { - "function": "schema", - "functionOptions": { - "schema": { - "type": "array", - "minItems": 1 - } - } - }, - "name": "openapi-tags", - "description": "OpenAPI object should have non-empty \"tags\" array.", - "message": "OpenAPI object should have non-empty \"tags\" array." - }, - "operation-description": { - "given": [ - "#Operation_Object" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "description" - }, - "name": "operation-description", - "description": "Operation \"description\" should be present and non-empty string.", - "message": "Operation \"description\" should be present and non-empty string." - }, - "operation-operationId": { - "given": [ - "#Operation_Object" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "operationId" - }, - "name": "operation-operationId", - "description": "Operation should have \"operationId\".", - "message": "Operation should have \"operationId\"." - }, - "operation-operationId-valid-in-url": { - "given": [ - "#Operation_Object" - ], - "severity": "error", - "then": { - "function": "pattern", - "functionOptions": { - "match": "^[A-Za-z0-9-._~:/?#\\[\\]@!\\$&'()*+,;=]*$" - }, - "field": "operationId" - }, - "name": "operation-operationId-valid-in-url", - "description": "operationId must not characters that are invalid when used in URL.", - "message": "OperationId should not have characters that are invalid when used in URL." - }, - "operation-singular-tag": { - "given": [ - "#API_Tags" - ], - "severity": "off", - "then": { - "function": "length", - "functionOptions": { - "max": 1 - } - }, - "name": "operation-singular-tag", - "description": "Operation should not have more than a single tag.", - "message": "Operation should not have more than a single tag." - }, - "operation-tags": { - "given": [ - "#Operation_Object" - ], - "severity": "warn", - "then": { - "function": "length", - "functionOptions": { - "max": 999, - "min": 1 - }, - "field": "tags" - }, - "name": "operation-tags", - "description": "Operation should have non-empty \"tags\" array.", - "message": "Operation should have non-empty \"tags\" array." - }, - "path-declarations-must-exist": { - "given": [ - "#Path_Item" - ], - "severity": "error", - "then": { - "function": "pattern", - "functionOptions": { - "notMatch": "{}" - }, - "field": "@key" - }, - "name": "path-declarations-must-exist", - "description": "Path parameter declarations must not be empty, ex.`/given/{}` is invalid.", - "message": "Path parameter declarations must not be empty, ex.\"/given/{}\" is invalid." - }, - "contact-name": { - "given": [ - "#API_Contact" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "name" - }, - "name": "contact-name", - "description": "Contact object should have \"name\"", - "message": "Contact object should have \"name\"" - }, - "path-keys-no-trailing-slash": { - "given": [ - "#Path_Object" - ], - "severity": "warn", - "then": { - "function": "pattern", - "functionOptions": { - "notMatch": ".+\\/$" - }, - "field": "@key" - }, - "name": "path-keys-no-trailing-slash", - "description": "Path should not end with slash.", - "message": "Path should not end with slash." - }, - "path-not-include-query": { - "given": [ - "#Path_Object" - ], - "severity": "warn", - "then": { - "function": "pattern", - "functionOptions": { - "notMatch": "\\?" - }, - "field": "@key" - }, - "name": "path-not-include-query", - "description": "Path should not include query string.", - "message": "Path should not include query string." - }, - "tag-description": { - "given": [ - "#API_Tags_Item" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "description" - }, - "name": "tag-description", - "description": "Tag object should have `description`.", - "message": "Tag object should have \"description\"." - }, - "api-servers": { - "given": [ - "#API_Server" - ], - "severity": "warn", - "then": { - "function": "schema", - "functionOptions": { - "schema": { - "type": "array", - "minItems": 1, - "items": { - "type": "object" - } - }, - "dialect": "draft7" - } - }, - "name": "api-servers", - "description": "OpenAPI document should have a server defined. \n\nThis can be localhost, a development or production server. \n\n**OpenAPI V3 example**\n\n```json\n{\n \"servers\": [\n {\n \"url\": \"https://staging.myprodserver.com/v1\",\n \"description\": \"Staging server\"\n },\n {\n \"url\": \"https://myprodserver.com/v1\",\n \"description\": \"Production server\"\n }\n ]\n}\n```\n\n**OpenAPI V2 example**\n\n```json\n{\n \"host\": \"myprodserver.com\",\n \"basePath\": \"/v2\",\n \"schemes\": [\n \"https\"\n ]\n}\n```\n\n", - "message": "Server should be present." - }, - "server-trailing-slash": { - "given": [ - "#API_Server_URL" - ], - "severity": "warn", - "then": { - "function": "pattern", - "functionOptions": { - "notMatch": "/$" - } - }, - "name": "server-trailing-slash", - "description": "Server URL should not have trailing slash.", - "message": "Server URL should not have trailing slash" - }, - "operation-success-response": { - "given": [ - "#Operation_Object" - ], - "severity": "warn", - "then": { - "function": "oasOpSuccessResponse", - "field": "responses" - }, - "name": "operation-success-response", - "description": "Operation should have at least one \"2xx\" or \"3xx\" response.", - "message": "Operation should have at least one \"2xx\" or \"3xx\" response." - }, - "path-params": { - "given": [ - "#API_Document" - ], - "severity": "error", - "then": { - "function": "oasPathParam" - }, - "name": "path-params", - "description": "Path parameters must be defined and valid.", - "message": "{{error}}" - }, - "operation-parameters": { - "given": [ - "#Operation_Object" - ], - "severity": "warn", - "then": { - "function": "oasOpParams", - "field": "parameters" - }, - "name": "operation-parameters", - "description": "Operation parameters are unique and non-repeating.", - "message": "Operation parameters are unique and non-repeating." - }, - "typed-enum": { - "given": [ - "#All_Enum" - ], - "severity": "warn", - "then": { - "function": "typedEnum" - }, - "name": "typed-enum", - "description": "Enum values should respect the specified type.", - "message": "{{error}}" - }, - "oas2-schema": { - "given": [ - "#API_Document" - ], - "severity": "error", - "then": { - "function": "oasDocumentSchema" - }, - "name": "oas2-schema", - "description": "Validate structure of OpenAPI v2 specification.", - "message": "{{error}}", - "formats": [ - "oas2" - ] - }, - "oas3-schema": { - "given": [ - "#API_Document" - ], - "severity": "error", - "then": { - "function": "oasDocumentSchema" - }, - "name": "oas3-schema", - "description": "Validate structure of OpenAPI v3 specification.", - "message": "{{error}}", - "formats": [ - "oas3" - ] - }, - "oas3-unused-component": { - "given": [ - "#API_Document" - ], - "severity": "warn", - "then": { - "function": "oasUnusedComponent" - }, - "name": "oas3-unused-component", - "description": "Validate structure of OpenAPI v3 specification.", - "message": "{{error}}", - "formats": [ - "oas3" - ] - }, - "operation-operationId-unique": { - "given": [ - "#API_Document" - ], - "severity": "error", - "then": { - "function": "oasOpIdUnique" - }, - "name": "operation-operationId-unique", - "description": "Every operation must have unique operationId.", - "message": "Every operation must have unique operationId" - }, - "oas2-operation-formData-consume-check": { - "given": [ - "#Operation_Object" - ], - "severity": "error", - "then": { - "function": "oasOpFormDataConsumeCheck" - }, - "name": "oas2-operation-formData-consume-check", - "description": "Operations with \"in: formData\" parameter must include \"application/x-www-form-urlencoded\" or \"multipart/form-data\" in their \"consumes\" property.", - "message": "Operations with \"in: formData\" parameter must include \"application/x-www-form-urlencoded\" or \"multipart/form-data\" in their \"consumes\" property.", - "formats": [ - "oas2" - ] - }, - "operation-tag-defined": { - "given": [ - "#API_Document" - ], - "severity": "warn", - "then": { - "function": "oasTagDefined" - }, - "name": "operation-tag-defined", - "description": "Operation tags must be defined in global tags.", - "message": "Operation tags must be defined in global tags" - }, - "no-$ref-siblings": { - "given": [ - "#All_Ref" - ], - "severity": "error", - "then": { - "function": "refSiblings" - }, - "name": "no-$ref-siblings", - "description": "Property must not be placed among $ref", - "message": "{{error}}", - "formats": [ - "oas3.0", - "oas2" - ] - }, - "oas2-operation-security-defined": { - "given": [ - "#API_Document" - ], - "severity": "warn", - "then": { - "function": "oasOpSecurityDefined", - "functionOptions": { - "schemesPath": [ - "securityDefinitions" - ] - } - }, - "name": "oas2-operation-security-defined", - "description": "Operation \"security\" values must match a scheme defined in the \"securityDefinitions\" object.", - "message": "{{error}}", - "formats": [ - "oas2" - ] - }, - "oas3-operation-security-defined": { - "given": [ - "#API_Document" - ], - "severity": "warn", - "then": { - "function": "oasOpSecurityDefined", - "functionOptions": { - "schemesPath": [ - "components", - "securitySchemes" - ] - } - }, - "name": "oas3-operation-security-defined", - "description": "Operation `security` values must match a scheme defined in the `components.securitySchemes` object.", - "message": "{{error}}", - "formats": [ - "oas3" - ] - }, - "duplicated-entry-in-enum": { - "given": [ - "#All_Enum" - ], - "severity": "warn", - "then": { - "function": "oasSchema", - "functionOptions": { - "schema": { - "type": "array", - "uniqueItems": true - } - }, - "field": "enum" - }, - "name": "duplicated-entry-in-enum", - "description": "Enum values should not have duplicate entry.", - "message": "{{error}}" - }, - "oas2-api-schemes": { - "given": [ - "#API_Document" - ], - "severity": "error", - "then": { - "function": "schema", - "functionOptions": { - "schema": { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - }, - "dialect": "draft7" - }, - "field": "schemes" - }, - "name": "oas2-api-schemes", - "description": "OpenAPI host \"schemes\" must be present and non-empty array.", - "message": "OpenAPI host \"schemes\" must be present and non-empty array", - "formats": [ - "oas2" - ] - }, - "oas2-discriminator": { - "given": [ - "#API_Document" - ], - "severity": "error", - "then": { - "function": "oasDiscriminator", - "field": "definitions[?(@.discriminator)]" - }, - "name": "oas2-discriminator", - "description": "Discriminator property must be defined and required", - "message": "Discriminator property must be defined and required", - "formats": [ - "oas2" - ] - }, - "server-not-example": { - "given": [ - "#API_Server_URL" - ], - "severity": "warn", - "then": { - "function": "pattern", - "functionOptions": { - "notMatch": "example.com" - } - }, - "name": "server-not-example", - "message": "Server URL must not point at example.com." - }, - "parameter-description": { - "given": [ - "#Request_Parameter_All" - ], - "severity": "warn", - "then": { - "function": "truthy", - "field": "description" - }, - "name": "parameter-description", - "message": "Parameter objects must have \"description\"." - }, - "oas2-anyOf": { - "given": [ - "#API_Document_RecursiveSearch" - ], - "severity": "warn", - "then": { - "function": "undefined", - "field": "anyOf" - }, - "name": "oas2-anyOf", - "description": "anyOf is not available in OpenAPI v2, it was added in OpenAPI v3", - "message": "anyOf is not available in OpenAPI v2, it was added in OpenAPI v3", - "formats": [ - "oas2" - ] - }, - "oas2-oneOf": { - "given": [ - "#API_Document_RecursiveSearch" - ], - "severity": "warn", - "then": { - "function": "undefined", - "field": "oneOf" - }, - "name": "oas2-oneOf", - "description": "anyOf is not available in OpenAPI v2, it was added in OpenAPI v3", - "message": "oneOf is not available in OpenAPI v2, it was added in OpenAPI v3", - "formats": [ - "oas2" - ] - }, - "oas3-examples-value-or-externalValue": { - "given": [ - "#All_Example" - ], - "severity": "warn", - "then": { - "function": "xor", - "functionOptions": { - "properties": [ - "externalValue", - "value" - ] - } - }, - "name": "oas3-examples-value-or-externalValue", - "description": "Examples must have either \"value\" or \"externalValue\" field.", - "message": "Examples must have either \"value\" or \"externalValue\" field.", - "formats": [ - "oas3" - ] - }, - "oas2-valid-schema-example": { - "given": [ - "#All_Example_Schema" - ], - "severity": "error", - "then": { - "function": "oasExample", - "functionOptions": { - "oasVersion": "2", - "schemaField": "$", - "type": "schema" - } - }, - "name": "oas2-valid-schema-example", - "description": "Examples must be valid against their defined schema.", - "message": "{{error}}", - "formats": [ - "oas2" - ] - }, - "oas3-valid-schema-example": { - "given": [ - "#All_Example_Schema" - ], - "severity": "error", - "then": { - "function": "oasExample", - "functionOptions": { - "oasVersion": "3", - "schemaField": "$", - "type": "schema" - } - }, - "name": "oas3-valid-schema-example", - "description": "Examples must be valid against their defined schema.", - "message": "{{error}}", - "formats": [ - "oas3" - ] - }, - "oas2-valid-media-example": { - "given": [ - "#All_Example_Media" - ], - "severity": "error", - "then": { - "function": "oasExample", - "functionOptions": { - "oasVersion": "2", - "schemaField": "schema", - "type": "media" - } - }, - "name": "oas2-valid-media-example", - "description": "Examples must be valid against their defined schema.", - "message": "{{error}}", - "formats": [ - "oas2" - ] - }, - "oas3-valid-media-example": { - "given": [ - "#All_Example_Media" - ], - "severity": "error", - "then": { - "function": "oasExample", - "functionOptions": { - "oasVersion": "3", - "schemaField": "schema", - "type": "media" - } - }, - "name": "oas3-valid-media-example", - "description": "Examples must be valid against their defined schema.", - "message": "{{error}}", - "formats": [ - "oas3" - ] - } - }, - "spectralExtends": [], - "name": "Stoplight Style Guide", - "x-embeddedFunctions": [ - { - "id": "7vlMlWLJeLfv6M_PHO36r", - "extendedFrom": "", - "name": "oasDocumentSchema", - "content": "import { createRulesetFunction } from '@stoplight/spectral-core';\nimport { schema as schemaFn } from '@stoplight/spectral-functions';\nimport { oas2, oas3_1 } from '@stoplight/spectral-formats';\n\nconst OAS_2 = {\n title: 'A JSON Schema for Swagger 2.0 API.',\n $id: 'http://swagger.io/v2/schema.json#',\n $schema: 'http://json-schema.org/draft-07/schema#',\n type: 'object',\n required: ['swagger', 'info', 'paths'],\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n swagger: {\n type: 'string',\n enum: ['2.0'],\n description: 'The Swagger version of this document.',\n },\n info: {\n $ref: '#/definitions/info',\n },\n host: {\n type: 'string',\n pattern: '^[^{}/ :\\\\\\\\]+(?::\\\\d+)?$',\n description: \"The host (name or ip) of the API. Example: 'swagger.io'\",\n },\n basePath: {\n type: 'string',\n pattern: '^/',\n description: \"The base path to the API. Example: '/api'.\",\n },\n schemes: {\n $ref: '#/definitions/schemesList',\n },\n consumes: {\n description: 'A list of MIME types accepted by the API.',\n allOf: [\n {\n $ref: '#/definitions/mediaTypeList',\n },\n ],\n },\n produces: {\n description: 'A list of MIME types the API can produce.',\n allOf: [\n {\n $ref: '#/definitions/mediaTypeList',\n },\n ],\n },\n paths: {\n $ref: '#/definitions/paths',\n },\n definitions: {\n $ref: '#/definitions/definitions',\n },\n parameters: {\n $ref: '#/definitions/parameterDefinitions',\n },\n responses: {\n $ref: '#/definitions/responseDefinitions',\n },\n security: {\n $ref: '#/definitions/security',\n },\n securityDefinitions: {\n $ref: '#/definitions/securityDefinitions',\n },\n tags: {\n type: 'array',\n items: {\n $ref: '#/definitions/tag',\n },\n uniqueItems: true,\n },\n externalDocs: {\n $ref: '#/definitions/externalDocs',\n },\n },\n definitions: {\n info: {\n type: 'object',\n description: 'General information about the API.',\n required: ['version', 'title'],\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n title: {\n type: 'string',\n description: 'A unique and precise title of the API.',\n },\n version: {\n type: 'string',\n description: 'A semantic version number of the API.',\n },\n description: {\n type: 'string',\n description:\n 'A longer description of the API. Should be different from the title. GitHub Flavored Markdown is allowed.',\n },\n termsOfService: {\n type: 'string',\n description: 'The terms of service for the API.',\n },\n contact: {\n $ref: '#/definitions/contact',\n },\n license: {\n $ref: '#/definitions/license',\n },\n },\n },\n contact: {\n type: 'object',\n description: 'Contact information for the owners of the API.',\n additionalProperties: false,\n properties: {\n name: {\n type: 'string',\n description: 'The identifying name of the contact person/organization.',\n },\n url: {\n type: 'string',\n description: 'The URL pointing to the contact information.',\n format: 'uri',\n },\n email: {\n type: 'string',\n description: 'The email address of the contact person/organization.',\n format: 'email',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n license: {\n type: 'object',\n required: ['name'],\n additionalProperties: false,\n properties: {\n name: {\n type: 'string',\n description: \"The name of the license type. It's encouraged to use an OSI compatible license.\",\n },\n url: {\n type: 'string',\n description: 'The URL pointing to the license.',\n format: 'uri',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n paths: {\n type: 'object',\n description: \"Relative paths to the individual endpoints. They must be relative to the 'basePath'.\",\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n '^/': {\n $ref: '#/definitions/pathItem',\n },\n },\n additionalProperties: false,\n },\n definitions: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/schema',\n },\n description: 'One or more JSON objects describing the schemas being consumed and produced by the API.',\n },\n parameterDefinitions: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/parameter',\n },\n description: 'One or more JSON representations for parameters',\n },\n responseDefinitions: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/response',\n },\n description: 'One or more JSON representations for responses',\n },\n externalDocs: {\n type: 'object',\n additionalProperties: false,\n description: 'information about external documentation',\n required: ['url'],\n properties: {\n description: {\n type: 'string',\n },\n url: {\n type: 'string',\n format: 'uri',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n examples: {\n type: 'object',\n additionalProperties: true,\n },\n mimeType: {\n type: 'string',\n description: 'The MIME type of the HTTP message.',\n },\n operation: {\n type: 'object',\n required: ['responses'],\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n tags: {\n type: 'array',\n items: {\n type: 'string',\n },\n uniqueItems: true,\n },\n summary: {\n type: 'string',\n description: 'A brief summary of the operation.',\n },\n description: {\n type: 'string',\n description: 'A longer description of the operation, GitHub Flavored Markdown is allowed.',\n },\n externalDocs: {\n $ref: '#/definitions/externalDocs',\n },\n operationId: {\n type: 'string',\n description: 'A unique identifier of the operation.',\n },\n produces: {\n description: 'A list of MIME types the API can produce.',\n allOf: [\n {\n $ref: '#/definitions/mediaTypeList',\n },\n ],\n },\n consumes: {\n description: 'A list of MIME types the API can consume.',\n allOf: [\n {\n $ref: '#/definitions/mediaTypeList',\n },\n ],\n },\n parameters: {\n $ref: '#/definitions/parametersList',\n },\n responses: {\n $ref: '#/definitions/responses',\n },\n schemes: {\n $ref: '#/definitions/schemesList',\n },\n deprecated: {\n type: 'boolean',\n default: false,\n },\n security: {\n $ref: '#/definitions/security',\n },\n },\n },\n pathItem: {\n type: 'object',\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n $ref: {\n type: 'string',\n },\n get: {\n $ref: '#/definitions/operation',\n },\n put: {\n $ref: '#/definitions/operation',\n },\n post: {\n $ref: '#/definitions/operation',\n },\n delete: {\n $ref: '#/definitions/operation',\n },\n options: {\n $ref: '#/definitions/operation',\n },\n head: {\n $ref: '#/definitions/operation',\n },\n patch: {\n $ref: '#/definitions/operation',\n },\n parameters: {\n $ref: '#/definitions/parametersList',\n },\n },\n },\n responses: {\n type: 'object',\n description: \"Response objects names can either be any valid HTTP status code or 'default'.\",\n minProperties: 1,\n additionalProperties: false,\n patternProperties: {\n '^([0-9]{3})$|^(default)$': {\n $ref: '#/definitions/responseValue',\n },\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n not: {\n type: 'object',\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n },\n responseValue: {\n oneOf: [\n {\n $ref: '#/definitions/response',\n },\n {\n $ref: '#/definitions/jsonReference',\n },\n ],\n },\n response: {\n type: 'object',\n required: ['description'],\n properties: {\n description: {\n type: 'string',\n },\n schema: {\n oneOf: [\n {\n $ref: '#/definitions/schema',\n },\n {\n $ref: '#/definitions/fileSchema',\n },\n ],\n },\n headers: {\n $ref: '#/definitions/headers',\n },\n examples: {\n $ref: '#/definitions/examples',\n },\n },\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n headers: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/header',\n },\n },\n header: {\n type: 'object',\n additionalProperties: false,\n required: ['type'],\n properties: {\n type: {\n type: 'string',\n enum: ['string', 'number', 'integer', 'boolean', 'array'],\n },\n format: {\n type: 'string',\n },\n items: {\n $ref: '#/definitions/primitivesItems',\n },\n collectionFormat: {\n $ref: '#/definitions/collectionFormat',\n },\n default: {\n $ref: '#/definitions/default',\n },\n maximum: {\n $ref: '#/definitions/maximum',\n },\n exclusiveMaximum: {\n $ref: '#/definitions/exclusiveMaximum',\n },\n minimum: {\n $ref: '#/definitions/minimum',\n },\n exclusiveMinimum: {\n $ref: '#/definitions/exclusiveMinimum',\n },\n maxLength: {\n $ref: '#/definitions/maxLength',\n },\n minLength: {\n $ref: '#/definitions/minLength',\n },\n pattern: {\n $ref: '#/definitions/pattern',\n },\n maxItems: {\n $ref: '#/definitions/maxItems',\n },\n minItems: {\n $ref: '#/definitions/minItems',\n },\n uniqueItems: {\n $ref: '#/definitions/uniqueItems',\n },\n enum: {\n $ref: '#/definitions/enum',\n },\n multipleOf: {\n $ref: '#/definitions/multipleOf',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n vendorExtension: {\n description: 'Any property starting with x- is valid.',\n additionalProperties: true,\n additionalItems: true,\n },\n bodyParameter: {\n type: 'object',\n required: ['name', 'in', 'schema'],\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n description: {\n type: 'string',\n description:\n 'A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed.',\n },\n name: {\n type: 'string',\n description: 'The name of the parameter.',\n },\n in: {\n type: 'string',\n description: 'Determines the location of the parameter.',\n enum: ['body'],\n },\n required: {\n type: 'boolean',\n description: 'Determines whether or not this parameter is required or optional.',\n default: false,\n },\n schema: {\n $ref: '#/definitions/schema',\n },\n },\n additionalProperties: false,\n },\n headerParameterSubSchema: {\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n required: {\n type: 'boolean',\n description: 'Determines whether or not this parameter is required or optional.',\n default: false,\n },\n in: {\n type: 'string',\n description: 'Determines the location of the parameter.',\n enum: ['header'],\n },\n description: {\n type: 'string',\n description:\n 'A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed.',\n },\n name: {\n type: 'string',\n description: 'The name of the parameter.',\n },\n type: {\n type: 'string',\n enum: ['string', 'number', 'boolean', 'integer', 'array'],\n },\n format: {\n type: 'string',\n },\n items: {\n $ref: '#/definitions/primitivesItems',\n },\n collectionFormat: {\n $ref: '#/definitions/collectionFormat',\n },\n default: {\n $ref: '#/definitions/default',\n },\n maximum: {\n $ref: '#/definitions/maximum',\n },\n exclusiveMaximum: {\n $ref: '#/definitions/exclusiveMaximum',\n },\n minimum: {\n $ref: '#/definitions/minimum',\n },\n exclusiveMinimum: {\n $ref: '#/definitions/exclusiveMinimum',\n },\n maxLength: {\n $ref: '#/definitions/maxLength',\n },\n minLength: {\n $ref: '#/definitions/minLength',\n },\n pattern: {\n $ref: '#/definitions/pattern',\n },\n maxItems: {\n $ref: '#/definitions/maxItems',\n },\n minItems: {\n $ref: '#/definitions/minItems',\n },\n uniqueItems: {\n $ref: '#/definitions/uniqueItems',\n },\n enum: {\n $ref: '#/definitions/enum',\n },\n multipleOf: {\n $ref: '#/definitions/multipleOf',\n },\n },\n },\n queryParameterSubSchema: {\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n required: {\n type: 'boolean',\n description: 'Determines whether or not this parameter is required or optional.',\n default: false,\n },\n in: {\n type: 'string',\n description: 'Determines the location of the parameter.',\n enum: ['query'],\n },\n description: {\n type: 'string',\n description:\n 'A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed.',\n },\n name: {\n type: 'string',\n description: 'The name of the parameter.',\n },\n allowEmptyValue: {\n type: 'boolean',\n default: false,\n description: 'allows sending a parameter by name only or with an empty value.',\n },\n type: {\n type: 'string',\n enum: ['string', 'number', 'boolean', 'integer', 'array'],\n },\n format: {\n type: 'string',\n },\n items: {\n $ref: '#/definitions/primitivesItems',\n },\n collectionFormat: {\n $ref: '#/definitions/collectionFormatWithMulti',\n },\n default: {\n $ref: '#/definitions/default',\n },\n maximum: {\n $ref: '#/definitions/maximum',\n },\n exclusiveMaximum: {\n $ref: '#/definitions/exclusiveMaximum',\n },\n minimum: {\n $ref: '#/definitions/minimum',\n },\n exclusiveMinimum: {\n $ref: '#/definitions/exclusiveMinimum',\n },\n maxLength: {\n $ref: '#/definitions/maxLength',\n },\n minLength: {\n $ref: '#/definitions/minLength',\n },\n pattern: {\n $ref: '#/definitions/pattern',\n },\n maxItems: {\n $ref: '#/definitions/maxItems',\n },\n minItems: {\n $ref: '#/definitions/minItems',\n },\n uniqueItems: {\n $ref: '#/definitions/uniqueItems',\n },\n enum: {\n $ref: '#/definitions/enum',\n },\n multipleOf: {\n $ref: '#/definitions/multipleOf',\n },\n },\n },\n formDataParameterSubSchema: {\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n required: {\n type: 'boolean',\n description: 'Determines whether or not this parameter is required or optional.',\n default: false,\n },\n in: {\n type: 'string',\n description: 'Determines the location of the parameter.',\n enum: ['formData'],\n },\n description: {\n type: 'string',\n description:\n 'A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed.',\n },\n name: {\n type: 'string',\n description: 'The name of the parameter.',\n },\n allowEmptyValue: {\n type: 'boolean',\n default: false,\n description: 'allows sending a parameter by name only or with an empty value.',\n },\n type: {\n type: 'string',\n enum: ['string', 'number', 'boolean', 'integer', 'array', 'file'],\n },\n format: {\n type: 'string',\n },\n items: {\n $ref: '#/definitions/primitivesItems',\n },\n collectionFormat: {\n $ref: '#/definitions/collectionFormatWithMulti',\n },\n default: {\n $ref: '#/definitions/default',\n },\n maximum: {\n $ref: '#/definitions/maximum',\n },\n exclusiveMaximum: {\n $ref: '#/definitions/exclusiveMaximum',\n },\n minimum: {\n $ref: '#/definitions/minimum',\n },\n exclusiveMinimum: {\n $ref: '#/definitions/exclusiveMinimum',\n },\n maxLength: {\n $ref: '#/definitions/maxLength',\n },\n minLength: {\n $ref: '#/definitions/minLength',\n },\n pattern: {\n $ref: '#/definitions/pattern',\n },\n maxItems: {\n $ref: '#/definitions/maxItems',\n },\n minItems: {\n $ref: '#/definitions/minItems',\n },\n uniqueItems: {\n $ref: '#/definitions/uniqueItems',\n },\n enum: {\n $ref: '#/definitions/enum',\n },\n multipleOf: {\n $ref: '#/definitions/multipleOf',\n },\n },\n },\n pathParameterSubSchema: {\n additionalProperties: false,\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n required: ['required'],\n properties: {\n required: {\n type: 'boolean',\n enum: [true],\n description: 'Determines whether or not this parameter is required or optional.',\n },\n in: {\n type: 'string',\n description: 'Determines the location of the parameter.',\n enum: ['path'],\n },\n description: {\n type: 'string',\n description:\n 'A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed.',\n },\n name: {\n type: 'string',\n description: 'The name of the parameter.',\n },\n type: {\n type: 'string',\n enum: ['string', 'number', 'boolean', 'integer', 'array'],\n },\n format: {\n type: 'string',\n },\n items: {\n $ref: '#/definitions/primitivesItems',\n },\n collectionFormat: {\n $ref: '#/definitions/collectionFormat',\n },\n default: {\n $ref: '#/definitions/default',\n },\n maximum: {\n $ref: '#/definitions/maximum',\n },\n exclusiveMaximum: {\n $ref: '#/definitions/exclusiveMaximum',\n },\n minimum: {\n $ref: '#/definitions/minimum',\n },\n exclusiveMinimum: {\n $ref: '#/definitions/exclusiveMinimum',\n },\n maxLength: {\n $ref: '#/definitions/maxLength',\n },\n minLength: {\n $ref: '#/definitions/minLength',\n },\n pattern: {\n $ref: '#/definitions/pattern',\n },\n maxItems: {\n $ref: '#/definitions/maxItems',\n },\n minItems: {\n $ref: '#/definitions/minItems',\n },\n uniqueItems: {\n $ref: '#/definitions/uniqueItems',\n },\n enum: {\n $ref: '#/definitions/enum',\n },\n multipleOf: {\n $ref: '#/definitions/multipleOf',\n },\n },\n },\n nonBodyParameter: {\n type: 'object',\n required: ['name', 'in', 'type'],\n oneOf: [\n {\n $ref: '#/definitions/headerParameterSubSchema',\n },\n {\n $ref: '#/definitions/formDataParameterSubSchema',\n },\n {\n $ref: '#/definitions/queryParameterSubSchema',\n },\n {\n $ref: '#/definitions/pathParameterSubSchema',\n },\n ],\n },\n parameter: {\n oneOf: [\n {\n $ref: '#/definitions/bodyParameter',\n },\n {\n $ref: '#/definitions/nonBodyParameter',\n },\n ],\n },\n schema: {\n type: 'object',\n description: 'A deterministic version of a JSON Schema object.',\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n properties: {\n $ref: {\n type: 'string',\n },\n format: {\n type: 'string',\n },\n title: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/title',\n },\n description: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/description',\n },\n default: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/default',\n },\n multipleOf: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/multipleOf',\n },\n maximum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/maximum',\n },\n exclusiveMaximum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum',\n },\n minimum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/minimum',\n },\n exclusiveMinimum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum',\n },\n maxLength: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveInteger',\n },\n minLength: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0',\n },\n pattern: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/pattern',\n },\n maxItems: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveInteger',\n },\n minItems: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0',\n },\n uniqueItems: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/uniqueItems',\n },\n maxProperties: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveInteger',\n },\n minProperties: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0',\n },\n required: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/stringArray',\n },\n enum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/enum',\n },\n additionalProperties: {\n anyOf: [\n {\n $ref: '#/definitions/schema',\n },\n {\n type: 'boolean',\n },\n ],\n default: {},\n },\n type: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/type',\n },\n items: {\n anyOf: [\n {\n $ref: '#/definitions/schema',\n },\n {\n type: 'array',\n minItems: 1,\n items: {\n $ref: '#/definitions/schema',\n },\n },\n ],\n default: {},\n },\n allOf: {\n type: 'array',\n minItems: 1,\n items: {\n $ref: '#/definitions/schema',\n },\n },\n oneOf: {\n type: 'array',\n minItems: 1,\n items: {\n $ref: '#/definitions/schema',\n },\n },\n anyOf: {\n type: 'array',\n minItems: 1,\n items: {\n $ref: '#/definitions/schema',\n },\n },\n properties: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/schema',\n },\n default: {},\n },\n discriminator: {\n type: 'string',\n },\n readOnly: {\n type: 'boolean',\n default: false,\n },\n xml: {\n $ref: '#/definitions/xml',\n },\n externalDocs: {\n $ref: '#/definitions/externalDocs',\n },\n example: {},\n },\n additionalProperties: false,\n },\n fileSchema: {\n type: 'object',\n description: 'A deterministic version of a JSON Schema object.',\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n required: ['type'],\n properties: {\n format: {\n type: 'string',\n },\n title: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/title',\n },\n description: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/description',\n },\n default: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/default',\n },\n required: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/stringArray',\n },\n type: {\n type: 'string',\n enum: ['file'],\n },\n readOnly: {\n type: 'boolean',\n default: false,\n },\n externalDocs: {\n $ref: '#/definitions/externalDocs',\n },\n example: {},\n },\n additionalProperties: false,\n },\n primitivesItems: {\n type: 'object',\n additionalProperties: false,\n properties: {\n type: {\n type: 'string',\n enum: ['string', 'number', 'integer', 'boolean', 'array'],\n },\n format: {\n type: 'string',\n },\n items: {\n $ref: '#/definitions/primitivesItems',\n },\n collectionFormat: {\n $ref: '#/definitions/collectionFormat',\n },\n default: {\n $ref: '#/definitions/default',\n },\n maximum: {\n $ref: '#/definitions/maximum',\n },\n exclusiveMaximum: {\n $ref: '#/definitions/exclusiveMaximum',\n },\n minimum: {\n $ref: '#/definitions/minimum',\n },\n exclusiveMinimum: {\n $ref: '#/definitions/exclusiveMinimum',\n },\n maxLength: {\n $ref: '#/definitions/maxLength',\n },\n minLength: {\n $ref: '#/definitions/minLength',\n },\n pattern: {\n $ref: '#/definitions/pattern',\n },\n maxItems: {\n $ref: '#/definitions/maxItems',\n },\n minItems: {\n $ref: '#/definitions/minItems',\n },\n uniqueItems: {\n $ref: '#/definitions/uniqueItems',\n },\n enum: {\n $ref: '#/definitions/enum',\n },\n multipleOf: {\n $ref: '#/definitions/multipleOf',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n security: {\n type: 'array',\n items: {\n $ref: '#/definitions/securityRequirement',\n },\n uniqueItems: true,\n },\n securityRequirement: {\n type: 'object',\n additionalProperties: {\n type: 'array',\n items: {\n type: 'string',\n },\n uniqueItems: true,\n },\n },\n xml: {\n type: 'object',\n additionalProperties: false,\n properties: {\n name: {\n type: 'string',\n },\n namespace: {\n type: 'string',\n },\n prefix: {\n type: 'string',\n },\n attribute: {\n type: 'boolean',\n default: false,\n },\n wrapped: {\n type: 'boolean',\n default: false,\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n tag: {\n type: 'object',\n additionalProperties: false,\n required: ['name'],\n properties: {\n name: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n externalDocs: {\n $ref: '#/definitions/externalDocs',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n securityDefinitions: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/basicAuthenticationSecurity',\n },\n {\n $ref: '#/definitions/apiKeySecurity',\n },\n {\n $ref: '#/definitions/oauth2ImplicitSecurity',\n },\n {\n $ref: '#/definitions/oauth2PasswordSecurity',\n },\n {\n $ref: '#/definitions/oauth2ApplicationSecurity',\n },\n {\n $ref: '#/definitions/oauth2AccessCodeSecurity',\n },\n ],\n },\n errorMessage: {\n properties: {\n basic: 'Invalid basic authentication security definition',\n apiKey: 'Invalid apiKey authentication security definition',\n oauth2: 'Invalid oauth2 authentication security definition',\n },\n _: 'Invalid security securityDefinitions',\n },\n },\n basicAuthenticationSecurity: {\n type: 'object',\n additionalProperties: false,\n required: ['type'],\n properties: {\n type: {\n type: 'string',\n enum: ['basic'],\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n apiKeySecurity: {\n type: 'object',\n additionalProperties: false,\n required: ['type', 'name', 'in'],\n properties: {\n type: {\n type: 'string',\n enum: ['apiKey'],\n },\n name: {\n type: 'string',\n },\n in: {\n type: 'string',\n enum: ['header', 'query'],\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n oauth2ImplicitSecurity: {\n type: 'object',\n additionalProperties: false,\n required: ['type', 'flow', 'authorizationUrl', 'scopes'],\n properties: {\n type: {\n type: 'string',\n enum: ['oauth2'],\n },\n flow: {\n type: 'string',\n enum: ['implicit'],\n },\n scopes: {\n $ref: '#/definitions/oauth2Scopes',\n },\n authorizationUrl: {\n type: 'string',\n format: 'uri',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n oauth2PasswordSecurity: {\n type: 'object',\n additionalProperties: false,\n required: ['type', 'flow', 'tokenUrl', 'scopes'],\n properties: {\n type: {\n type: 'string',\n enum: ['oauth2'],\n },\n flow: {\n type: 'string',\n enum: ['password'],\n },\n scopes: {\n $ref: '#/definitions/oauth2Scopes',\n },\n tokenUrl: {\n type: 'string',\n format: 'uri',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n oauth2ApplicationSecurity: {\n type: 'object',\n additionalProperties: false,\n required: ['type', 'flow', 'tokenUrl', 'scopes'],\n properties: {\n type: {\n type: 'string',\n enum: ['oauth2'],\n },\n flow: {\n type: 'string',\n enum: ['application'],\n },\n scopes: {\n $ref: '#/definitions/oauth2Scopes',\n },\n tokenUrl: {\n type: 'string',\n format: 'uri',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n oauth2AccessCodeSecurity: {\n type: 'object',\n additionalProperties: false,\n required: ['type', 'flow', 'authorizationUrl', 'tokenUrl', 'scopes'],\n properties: {\n type: {\n type: 'string',\n enum: ['oauth2'],\n },\n flow: {\n type: 'string',\n enum: ['accessCode'],\n },\n scopes: {\n $ref: '#/definitions/oauth2Scopes',\n },\n authorizationUrl: {\n type: 'string',\n format: 'uri',\n },\n tokenUrl: {\n type: 'string',\n format: 'uri',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {\n $ref: '#/definitions/vendorExtension',\n },\n },\n },\n oauth2Scopes: {\n type: 'object',\n additionalProperties: {\n type: 'string',\n },\n },\n mediaTypeList: {\n type: 'array',\n items: {\n $ref: '#/definitions/mimeType',\n },\n uniqueItems: true,\n },\n parametersList: {\n type: 'array',\n description: 'The parameters needed to send a valid API call.',\n additionalItems: false,\n items: {\n oneOf: [\n {\n $ref: '#/definitions/parameter',\n },\n {\n $ref: '#/definitions/jsonReference',\n },\n ],\n },\n uniqueItems: true,\n },\n schemesList: {\n type: 'array',\n description: 'The transfer protocol of the API.',\n items: {\n type: 'string',\n enum: ['http', 'https', 'ws', 'wss'],\n },\n uniqueItems: true,\n },\n collectionFormat: {\n type: 'string',\n enum: ['csv', 'ssv', 'tsv', 'pipes'],\n default: 'csv',\n },\n collectionFormatWithMulti: {\n type: 'string',\n enum: ['csv', 'ssv', 'tsv', 'pipes', 'multi'],\n default: 'csv',\n },\n title: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/title',\n },\n description: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/description',\n },\n default: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/default',\n },\n multipleOf: {\n type: 'number',\n exclusiveMinimum: 0,\n },\n maximum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/maximum',\n },\n exclusiveMaximum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum',\n },\n minimum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/minimum',\n },\n exclusiveMinimum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum',\n },\n maxLength: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveInteger',\n },\n minLength: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0',\n },\n pattern: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/pattern',\n },\n maxItems: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveInteger',\n },\n minItems: {\n $ref: 'http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0',\n },\n uniqueItems: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/uniqueItems',\n },\n enum: {\n $ref: 'http://json-schema.org/draft-04/schema#/properties/enum',\n },\n jsonReference: {\n type: 'object',\n required: ['$ref'],\n additionalProperties: false,\n properties: {\n $ref: {\n type: 'string',\n },\n },\n },\n },\n};\n\nconst OAS_3 = {\n $id: 'https://spec.openapis.org/oas/3.0/schema/2019-04-02',\n $schema: 'http://json-schema.org/draft-07/schema#',\n description: 'Validation schema for OpenAPI Specification 3.0.X.',\n type: 'object',\n required: ['openapi', 'info', 'paths'],\n properties: {\n openapi: {\n type: 'string',\n pattern: '^3\\\\.0\\\\.\\\\d(-.+)?$',\n },\n info: {\n $ref: '#/definitions/Info',\n },\n externalDocs: {\n $ref: '#/definitions/ExternalDocumentation',\n },\n servers: {\n type: 'array',\n items: {\n $ref: '#/definitions/Server',\n },\n },\n security: {\n type: 'array',\n items: {\n $ref: '#/definitions/SecurityRequirement',\n },\n },\n tags: {\n type: 'array',\n items: {\n $ref: '#/definitions/Tag',\n },\n uniqueItems: true,\n },\n paths: {\n $ref: '#/definitions/Paths',\n },\n components: {\n $ref: '#/definitions/Components',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n definitions: {\n Reference: {\n type: 'object',\n required: ['$ref'],\n patternProperties: {\n '^\\\\$ref$': {\n type: 'string',\n format: 'uri-reference',\n },\n },\n },\n Info: {\n type: 'object',\n required: ['title', 'version'],\n properties: {\n title: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n termsOfService: {\n type: 'string',\n format: 'uri-reference',\n },\n contact: {\n $ref: '#/definitions/Contact',\n },\n license: {\n $ref: '#/definitions/License',\n },\n version: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Contact: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n url: {\n type: 'string',\n format: 'uri-reference',\n },\n email: {\n type: 'string',\n format: 'email',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n License: {\n type: 'object',\n required: ['name'],\n properties: {\n name: {\n type: 'string',\n },\n url: {\n type: 'string',\n format: 'uri-reference',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Server: {\n type: 'object',\n required: ['url'],\n properties: {\n url: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n variables: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/ServerVariable',\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n ServerVariable: {\n type: 'object',\n required: ['default'],\n properties: {\n enum: {\n type: 'array',\n items: {\n type: 'string',\n },\n },\n default: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Components: {\n type: 'object',\n properties: {\n schemas: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n },\n responses: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/Response',\n },\n ],\n },\n },\n },\n parameters: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/Parameter',\n },\n ],\n },\n },\n },\n examples: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/Example',\n },\n ],\n },\n },\n },\n requestBodies: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/RequestBody',\n },\n ],\n },\n },\n },\n headers: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/Header',\n },\n ],\n },\n },\n },\n securitySchemes: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/SecurityScheme',\n },\n ],\n },\n },\n },\n links: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/Link',\n },\n ],\n },\n },\n },\n callbacks: {\n type: 'object',\n patternProperties: {\n '^[a-zA-Z0-9\\\\.\\\\-_]+$': {\n oneOf: [\n {\n $ref: '#/definitions/Reference',\n },\n {\n $ref: '#/definitions/Callback',\n },\n ],\n },\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Schema: {\n type: 'object',\n properties: {\n title: {\n type: 'string',\n },\n multipleOf: {\n type: 'number',\n exclusiveMinimum: 0,\n },\n maximum: {\n type: 'number',\n },\n exclusiveMaximum: {\n type: 'boolean',\n default: false,\n },\n minimum: {\n type: 'number',\n },\n exclusiveMinimum: {\n type: 'boolean',\n default: false,\n },\n maxLength: {\n type: 'integer',\n minimum: 0,\n },\n minLength: {\n type: 'integer',\n minimum: 0,\n default: 0,\n },\n pattern: {\n type: 'string',\n format: 'regex',\n },\n maxItems: {\n type: 'integer',\n minimum: 0,\n },\n minItems: {\n type: 'integer',\n minimum: 0,\n default: 0,\n },\n uniqueItems: {\n type: 'boolean',\n default: false,\n },\n maxProperties: {\n type: 'integer',\n minimum: 0,\n },\n minProperties: {\n type: 'integer',\n minimum: 0,\n default: 0,\n },\n required: {\n type: 'array',\n items: {\n type: 'string',\n },\n minItems: 1,\n uniqueItems: true,\n },\n enum: {\n type: 'array',\n items: {},\n minItems: 1,\n uniqueItems: false,\n },\n type: {\n type: 'string',\n enum: ['array', 'boolean', 'integer', 'number', 'object', 'string'],\n },\n not: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n allOf: {\n type: 'array',\n items: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n oneOf: {\n type: 'array',\n items: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n anyOf: {\n type: 'array',\n items: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n items: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n properties: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n {\n type: 'boolean',\n },\n ],\n default: true,\n },\n description: {\n type: 'string',\n },\n format: {\n type: 'string',\n },\n default: {},\n nullable: {\n type: 'boolean',\n default: false,\n },\n discriminator: {\n $ref: '#/definitions/Discriminator',\n },\n readOnly: {\n type: 'boolean',\n default: false,\n },\n writeOnly: {\n type: 'boolean',\n default: false,\n },\n example: {},\n externalDocs: {\n $ref: '#/definitions/ExternalDocumentation',\n },\n deprecated: {\n type: 'boolean',\n default: false,\n },\n xml: {\n $ref: '#/definitions/XML',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Discriminator: {\n type: 'object',\n required: ['propertyName'],\n properties: {\n propertyName: {\n type: 'string',\n },\n mapping: {\n type: 'object',\n additionalProperties: {\n type: 'string',\n },\n },\n },\n },\n XML: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n namespace: {\n type: 'string',\n format: 'uri',\n },\n prefix: {\n type: 'string',\n },\n attribute: {\n type: 'boolean',\n default: false,\n },\n wrapped: {\n type: 'boolean',\n default: false,\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Response: {\n type: 'object',\n required: ['description'],\n properties: {\n description: {\n type: 'string',\n },\n headers: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Header',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n content: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/MediaType',\n },\n },\n links: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Link',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n MediaType: {\n type: 'object',\n properties: {\n schema: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n example: {},\n examples: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Example',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n encoding: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/Encoding',\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n allOf: [\n {\n $ref: '#/definitions/ExampleXORExamples',\n },\n ],\n },\n Example: {\n type: 'object',\n properties: {\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n value: {},\n externalValue: {\n type: 'string',\n format: 'uri-reference',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Header: {\n type: 'object',\n properties: {\n description: {\n type: 'string',\n },\n required: {\n type: 'boolean',\n default: false,\n },\n deprecated: {\n type: 'boolean',\n default: false,\n },\n allowEmptyValue: {\n type: 'boolean',\n default: false,\n },\n style: {\n type: 'string',\n enum: ['simple'],\n default: 'simple',\n },\n explode: {\n type: 'boolean',\n },\n allowReserved: {\n type: 'boolean',\n default: false,\n },\n schema: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n content: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/MediaType',\n },\n minProperties: 1,\n maxProperties: 1,\n },\n example: {},\n examples: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Example',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n allOf: [\n {\n $ref: '#/definitions/ExampleXORExamples',\n },\n {\n $ref: '#/definitions/SchemaXORContent',\n },\n ],\n },\n Paths: {\n type: 'object',\n patternProperties: {\n '^\\\\/': {\n $ref: '#/definitions/PathItem',\n },\n '^x-': {},\n },\n additionalProperties: false,\n },\n PathItem: {\n type: 'object',\n properties: {\n $ref: {\n type: 'string',\n },\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n servers: {\n type: 'array',\n items: {\n $ref: '#/definitions/Server',\n },\n },\n parameters: {\n type: 'array',\n items: {\n oneOf: [\n {\n $ref: '#/definitions/Parameter',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n uniqueItems: true,\n },\n },\n patternProperties: {\n '^(get|put|post|delete|options|head|patch|trace)$': {\n $ref: '#/definitions/Operation',\n },\n '^x-': {},\n },\n additionalProperties: false,\n },\n Operation: {\n type: 'object',\n required: ['responses'],\n properties: {\n tags: {\n type: 'array',\n items: {\n type: 'string',\n },\n },\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n externalDocs: {\n $ref: '#/definitions/ExternalDocumentation',\n },\n operationId: {\n type: 'string',\n },\n parameters: {\n type: 'array',\n items: {\n oneOf: [\n {\n $ref: '#/definitions/Parameter',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n uniqueItems: true,\n },\n requestBody: {\n oneOf: [\n {\n $ref: '#/definitions/RequestBody',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n responses: {\n $ref: '#/definitions/Responses',\n },\n callbacks: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Callback',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n deprecated: {\n type: 'boolean',\n default: false,\n },\n security: {\n type: 'array',\n items: {\n $ref: '#/definitions/SecurityRequirement',\n },\n },\n servers: {\n type: 'array',\n items: {\n $ref: '#/definitions/Server',\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Responses: {\n type: 'object',\n properties: {\n default: {\n oneOf: [\n {\n $ref: '#/definitions/Response',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n patternProperties: {\n '^[1-5](?:\\\\d{2}|XX)$': {\n oneOf: [\n {\n $ref: '#/definitions/Response',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n '^x-': {},\n },\n minProperties: 1,\n additionalProperties: false,\n },\n SecurityRequirement: {\n type: 'object',\n additionalProperties: {\n type: 'array',\n items: {\n type: 'string',\n },\n },\n },\n Tag: {\n type: 'object',\n required: ['name'],\n properties: {\n name: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n externalDocs: {\n $ref: '#/definitions/ExternalDocumentation',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n ExternalDocumentation: {\n type: 'object',\n required: ['url'],\n properties: {\n description: {\n type: 'string',\n },\n url: {\n type: 'string',\n format: 'uri-reference',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n ExampleXORExamples: {\n description: 'Example and examples are mutually exclusive',\n not: {\n required: ['example', 'examples'],\n },\n },\n SchemaXORContent: {\n description: 'Schema and content are mutually exclusive, at least one is required',\n not: {\n required: ['schema', 'content'],\n },\n oneOf: [\n {\n required: ['schema'],\n },\n {\n required: ['content'],\n description: 'Some properties are not allowed if content is present',\n allOf: [\n {\n not: {\n required: ['style'],\n },\n },\n {\n not: {\n required: ['explode'],\n },\n },\n {\n not: {\n required: ['allowReserved'],\n },\n },\n {\n not: {\n required: ['example'],\n },\n },\n {\n not: {\n required: ['examples'],\n },\n },\n ],\n },\n ],\n },\n Parameter: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n in: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n required: {\n type: 'boolean',\n default: false,\n },\n deprecated: {\n type: 'boolean',\n default: false,\n },\n allowEmptyValue: {\n type: 'boolean',\n default: false,\n },\n style: {\n type: 'string',\n },\n explode: {\n type: 'boolean',\n },\n allowReserved: {\n type: 'boolean',\n default: false,\n },\n schema: {\n oneOf: [\n {\n $ref: '#/definitions/Schema',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n content: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/MediaType',\n },\n minProperties: 1,\n maxProperties: 1,\n },\n example: {},\n examples: {\n type: 'object',\n additionalProperties: {\n oneOf: [\n {\n $ref: '#/definitions/Example',\n },\n {\n $ref: '#/definitions/Reference',\n },\n ],\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n required: ['name', 'in'],\n allOf: [\n {\n $ref: '#/definitions/ExampleXORExamples',\n },\n {\n $ref: '#/definitions/SchemaXORContent',\n },\n {\n $ref: '#/definitions/ParameterLocation',\n },\n ],\n },\n ParameterLocation: {\n description: 'Parameter location',\n oneOf: [\n {\n description: 'Parameter in path',\n required: ['required'],\n properties: {\n in: {\n enum: ['path'],\n },\n style: {\n enum: ['matrix', 'label', 'simple'],\n default: 'simple',\n },\n required: {\n enum: [true],\n },\n },\n },\n {\n description: 'Parameter in query',\n properties: {\n in: {\n enum: ['query'],\n },\n style: {\n enum: ['form', 'spaceDelimited', 'pipeDelimited', 'deepObject'],\n default: 'form',\n },\n },\n },\n {\n description: 'Parameter in header',\n properties: {\n in: {\n enum: ['header'],\n },\n style: {\n enum: ['simple'],\n default: 'simple',\n },\n },\n },\n {\n description: 'Parameter in cookie',\n properties: {\n in: {\n enum: ['cookie'],\n },\n style: {\n enum: ['form'],\n default: 'form',\n },\n },\n },\n ],\n },\n RequestBody: {\n type: 'object',\n required: ['content'],\n properties: {\n description: {\n type: 'string',\n },\n content: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/MediaType',\n },\n },\n required: {\n type: 'boolean',\n default: false,\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n SecurityScheme: {\n oneOf: [\n {\n $ref: '#/definitions/APIKeySecurityScheme',\n },\n {\n $ref: '#/definitions/HTTPSecurityScheme',\n },\n {\n $ref: '#/definitions/OAuth2SecurityScheme',\n },\n {\n $ref: '#/definitions/OpenIdConnectSecurityScheme',\n },\n ],\n },\n APIKeySecurityScheme: {\n type: 'object',\n required: ['type', 'name', 'in'],\n properties: {\n type: {\n type: 'string',\n enum: ['apiKey'],\n },\n name: {\n type: 'string',\n },\n in: {\n type: 'string',\n enum: ['header', 'query', 'cookie'],\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n HTTPSecurityScheme: {\n type: 'object',\n required: ['scheme', 'type'],\n properties: {\n scheme: {\n type: 'string',\n },\n bearerFormat: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n type: {\n type: 'string',\n enum: ['http'],\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n oneOf: [\n {\n description: 'Bearer',\n properties: {\n scheme: {\n enum: ['bearer'],\n },\n },\n },\n {\n description: 'Non Bearer',\n not: {\n required: ['bearerFormat'],\n },\n properties: {\n scheme: {\n not: {\n enum: ['bearer'],\n },\n },\n },\n },\n ],\n },\n OAuth2SecurityScheme: {\n type: 'object',\n required: ['type', 'flows'],\n properties: {\n type: {\n type: 'string',\n enum: ['oauth2'],\n },\n flows: {\n $ref: '#/definitions/OAuthFlows',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n OpenIdConnectSecurityScheme: {\n type: 'object',\n required: ['type', 'openIdConnectUrl'],\n properties: {\n type: {\n type: 'string',\n enum: ['openIdConnect'],\n },\n openIdConnectUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n description: {\n type: 'string',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n OAuthFlows: {\n type: 'object',\n properties: {\n implicit: {\n $ref: '#/definitions/ImplicitOAuthFlow',\n },\n password: {\n $ref: '#/definitions/PasswordOAuthFlow',\n },\n clientCredentials: {\n $ref: '#/definitions/ClientCredentialsFlow',\n },\n authorizationCode: {\n $ref: '#/definitions/AuthorizationCodeOAuthFlow',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n ImplicitOAuthFlow: {\n type: 'object',\n required: ['authorizationUrl', 'scopes'],\n properties: {\n authorizationUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n refreshUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n scopes: {\n type: 'object',\n additionalProperties: {\n type: 'string',\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n PasswordOAuthFlow: {\n type: 'object',\n required: ['tokenUrl', 'scopes'],\n properties: {\n tokenUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n refreshUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n scopes: {\n type: 'object',\n additionalProperties: {\n type: 'string',\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n ClientCredentialsFlow: {\n type: 'object',\n required: ['tokenUrl', 'scopes'],\n properties: {\n tokenUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n refreshUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n scopes: {\n type: 'object',\n additionalProperties: {\n type: 'string',\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n AuthorizationCodeOAuthFlow: {\n type: 'object',\n required: ['authorizationUrl', 'tokenUrl', 'scopes'],\n properties: {\n authorizationUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n tokenUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n refreshUrl: {\n type: 'string',\n format: 'uri-reference',\n },\n scopes: {\n type: 'object',\n additionalProperties: {\n type: 'string',\n },\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n },\n Link: {\n type: 'object',\n properties: {\n operationId: {\n type: 'string',\n },\n operationRef: {\n type: 'string',\n format: 'uri-reference',\n },\n parameters: {\n type: 'object',\n additionalProperties: {},\n },\n requestBody: {},\n description: {\n type: 'string',\n },\n server: {\n $ref: '#/definitions/Server',\n },\n },\n patternProperties: {\n '^x-': {},\n },\n additionalProperties: false,\n not: {\n description: 'Operation Id and Operation Ref are mutually exclusive',\n required: ['operationId', 'operationRef'],\n },\n },\n Callback: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/PathItem',\n },\n patternProperties: {\n '^x-': {},\n },\n },\n Encoding: {\n type: 'object',\n properties: {\n contentType: {\n type: 'string',\n },\n headers: {\n type: 'object',\n additionalProperties: {\n $ref: '#/definitions/Header',\n },\n },\n style: {\n type: 'string',\n enum: ['form', 'spaceDelimited', 'pipeDelimited', 'deepObject'],\n },\n explode: {\n type: 'boolean',\n },\n allowReserved: {\n type: 'boolean',\n default: false,\n },\n },\n additionalProperties: false,\n },\n },\n};\n\nconst OAS_3_1 = {\n $id: 'https://spec.openapis.org/oas/3.1/schema/2021-09-28',\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n type: 'object',\n properties: {\n openapi: {\n type: 'string',\n pattern: '^3\\\\.1\\\\.\\\\d+(-.+)?$',\n },\n info: {\n $ref: '#/$defs/info',\n },\n jsonSchemaDialect: {\n type: 'string',\n format: 'uri',\n default: 'https://spec.openapis.org/oas/3.1/dialect/base',\n },\n servers: {\n type: 'array',\n items: {\n $ref: '#/$defs/server',\n },\n },\n paths: {\n $ref: '#/$defs/paths',\n },\n webhooks: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/path-item-or-reference',\n },\n },\n components: {\n $ref: '#/$defs/components',\n },\n security: {\n type: 'array',\n items: {\n $ref: '#/$defs/security-requirement',\n },\n },\n tags: {\n type: 'array',\n items: {\n $ref: '#/$defs/tag',\n },\n },\n externalDocs: {\n $ref: '#/$defs/external-documentation',\n },\n },\n required: ['openapi', 'info'],\n anyOf: [\n {\n required: ['paths'],\n errorMessage: 'The document must have either \"paths\", \"webhooks\" or \"components\"',\n },\n {\n required: ['components'],\n },\n {\n required: ['webhooks'],\n },\n ],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n $defs: {\n info: {\n type: 'object',\n properties: {\n title: {\n type: 'string',\n },\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n termsOfService: {\n type: 'string',\n },\n contact: {\n $ref: '#/$defs/contact',\n },\n license: {\n $ref: '#/$defs/license',\n },\n version: {\n type: 'string',\n },\n },\n required: ['title', 'version'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n contact: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n url: {\n type: 'string',\n },\n email: {\n type: 'string',\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n license: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n identifier: {\n type: 'string',\n },\n url: {\n type: 'string',\n format: 'uri',\n },\n },\n required: ['name'],\n oneOf: [\n {\n required: ['identifier'],\n },\n {\n required: ['url'],\n },\n ],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n server: {\n type: 'object',\n properties: {\n url: {\n type: 'string',\n format: 'uri-template',\n },\n description: {\n type: 'string',\n },\n variables: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/server-variable',\n },\n },\n },\n required: ['url'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'server-variable': {\n type: 'object',\n properties: {\n enum: {\n type: 'array',\n items: {\n type: 'string',\n },\n minItems: 1,\n },\n default: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n },\n required: ['default'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n components: {\n type: 'object',\n properties: {\n schemas: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/schema',\n },\n },\n responses: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/response-or-reference',\n },\n },\n parameters: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/parameter-or-reference',\n },\n },\n examples: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/example-or-reference',\n },\n },\n requestBodies: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/request-body-or-reference',\n },\n },\n headers: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/header-or-reference',\n },\n },\n securitySchemes: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/security-scheme-or-reference',\n },\n },\n links: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/link-or-reference',\n },\n },\n callbacks: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/callbacks-or-reference',\n },\n },\n pathItems: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/path-item-or-reference',\n },\n },\n },\n patternProperties: {\n '^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$': {\n $comment:\n 'Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected',\n propertyNames: {\n pattern: '^[a-zA-Z0-9._-]+$',\n },\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n paths: {\n type: 'object',\n patternProperties: {\n '^/': {\n $ref: '#/$defs/path-item',\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'path-item': {\n type: 'object',\n properties: {\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n servers: {\n type: 'array',\n items: {\n $ref: '#/$defs/server',\n },\n },\n parameters: {\n type: 'array',\n items: {\n $ref: '#/$defs/parameter-or-reference',\n },\n },\n },\n patternProperties: {\n '^(get|put|post|delete|options|head|patch|trace)$': {\n $ref: '#/$defs/operation',\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'path-item-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/path-item',\n },\n },\n operation: {\n type: 'object',\n properties: {\n tags: {\n type: 'array',\n items: {\n type: 'string',\n },\n },\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n externalDocs: {\n $ref: '#/$defs/external-documentation',\n },\n operationId: {\n type: 'string',\n },\n parameters: {\n type: 'array',\n items: {\n $ref: '#/$defs/parameter-or-reference',\n },\n },\n requestBody: {\n $ref: '#/$defs/request-body-or-reference',\n },\n responses: {\n $ref: '#/$defs/responses',\n },\n callbacks: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/callbacks-or-reference',\n },\n },\n deprecated: {\n default: false,\n type: 'boolean',\n },\n security: {\n type: 'array',\n items: {\n $ref: '#/$defs/security-requirement',\n },\n },\n servers: {\n type: 'array',\n items: {\n $ref: '#/$defs/server',\n },\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'external-documentation': {\n type: 'object',\n properties: {\n description: {\n type: 'string',\n },\n url: {\n type: 'string',\n format: 'uri',\n },\n },\n required: ['url'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n parameter: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n in: {\n enum: ['query', 'header', 'path', 'cookie'],\n },\n description: {\n type: 'string',\n },\n required: {\n default: false,\n type: 'boolean',\n },\n deprecated: {\n default: false,\n type: 'boolean',\n },\n allowEmptyValue: {\n default: false,\n type: 'boolean',\n },\n schema: {\n $ref: '#/$defs/schema',\n },\n content: {\n $ref: '#/$defs/content',\n },\n },\n required: ['in'],\n oneOf: [\n {\n required: ['schema'],\n },\n {\n required: ['content'],\n },\n ],\n dependentSchemas: {\n schema: {\n properties: {\n style: {\n type: 'string',\n },\n explode: {\n type: 'boolean',\n },\n allowReserved: {\n default: false,\n type: 'boolean',\n },\n },\n allOf: [\n {\n $ref: '#/$defs/examples',\n },\n {\n $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-path',\n },\n {\n $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-header',\n },\n {\n $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-query',\n },\n {\n $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-cookie',\n },\n {\n $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-form',\n },\n ],\n $defs: {\n 'styles-for-path': {\n if: {\n properties: {\n in: {\n const: 'path',\n },\n },\n required: ['in'],\n },\n then: {\n properties: {\n name: {\n pattern: '[^/#?]+$',\n },\n style: {\n default: 'simple',\n enum: ['matrix', 'label', 'simple'],\n },\n required: {\n const: true,\n },\n },\n required: ['required'],\n },\n },\n 'styles-for-header': {\n if: {\n properties: {\n in: {\n const: 'header',\n },\n },\n required: ['in'],\n },\n then: {\n properties: {\n style: {\n default: 'simple',\n const: 'simple',\n },\n },\n },\n },\n 'styles-for-query': {\n if: {\n properties: {\n in: {\n const: 'query',\n },\n },\n required: ['in'],\n },\n then: {\n properties: {\n style: {\n default: 'form',\n enum: ['form', 'spaceDelimited', 'pipeDelimited', 'deepObject'],\n },\n },\n },\n },\n 'styles-for-cookie': {\n if: {\n properties: {\n in: {\n const: 'cookie',\n },\n },\n required: ['in'],\n },\n then: {\n properties: {\n style: {\n default: 'form',\n const: 'form',\n },\n },\n },\n },\n 'styles-for-form': {\n if: {\n properties: {\n style: {\n const: 'form',\n },\n },\n required: ['style'],\n },\n then: {\n properties: {\n explode: {\n default: true,\n },\n },\n },\n else: {\n properties: {\n explode: {\n default: false,\n },\n },\n },\n },\n },\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'parameter-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/parameter',\n },\n },\n 'request-body': {\n type: 'object',\n properties: {\n description: {\n type: 'string',\n },\n content: {\n $ref: '#/$defs/content',\n },\n required: {\n default: false,\n type: 'boolean',\n },\n },\n required: ['content'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'request-body-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/request-body',\n },\n },\n content: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/media-type',\n },\n propertyNames: {\n format: 'media-range',\n },\n },\n 'media-type': {\n type: 'object',\n properties: {\n schema: {\n $ref: '#/$defs/schema',\n },\n encoding: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/encoding',\n },\n },\n },\n allOf: [\n {\n $ref: '#/$defs/specification-extensions',\n },\n {\n $ref: '#/$defs/examples',\n },\n ],\n unevaluatedProperties: false,\n },\n encoding: {\n type: 'object',\n properties: {\n contentType: {\n type: 'string',\n format: 'media-range',\n },\n headers: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/header-or-reference',\n },\n },\n style: {\n default: 'form',\n enum: ['form', 'spaceDelimited', 'pipeDelimited', 'deepObject'],\n },\n explode: {\n type: 'boolean',\n },\n allowReserved: {\n default: false,\n type: 'boolean',\n },\n },\n allOf: [\n {\n $ref: '#/$defs/specification-extensions',\n },\n {\n $ref: '#/$defs/encoding/$defs/explode-default',\n },\n ],\n unevaluatedProperties: false,\n $defs: {\n 'explode-default': {\n if: {\n properties: {\n style: {\n const: 'form',\n },\n },\n required: ['style'],\n },\n then: {\n properties: {\n explode: {\n default: true,\n },\n },\n },\n else: {\n properties: {\n explode: {\n default: false,\n },\n },\n },\n },\n },\n },\n responses: {\n type: 'object',\n properties: {\n default: {\n $ref: '#/$defs/response-or-reference',\n },\n },\n patternProperties: {\n '^[1-5](?:[0-9]{2}|XX)$': {\n $ref: '#/$defs/response-or-reference',\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n response: {\n type: 'object',\n properties: {\n description: {\n type: 'string',\n },\n headers: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/header-or-reference',\n },\n },\n content: {\n $ref: '#/$defs/content',\n },\n links: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/link-or-reference',\n },\n },\n },\n required: ['description'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'response-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/response',\n },\n },\n callbacks: {\n type: 'object',\n $ref: '#/$defs/specification-extensions',\n additionalProperties: {\n $ref: '#/$defs/path-item-or-reference',\n },\n },\n 'callbacks-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/callbacks',\n },\n },\n example: {\n type: 'object',\n properties: {\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n value: true,\n externalValue: {\n type: 'string',\n format: 'uri',\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'example-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/example',\n },\n },\n link: {\n type: 'object',\n properties: {\n operationRef: {\n type: 'string',\n format: 'uri-reference',\n },\n operationId: true,\n parameters: {\n $ref: '#/$defs/map-of-strings',\n },\n requestBody: true,\n description: {\n type: 'string',\n },\n body: {\n $ref: '#/$defs/server',\n },\n },\n oneOf: [\n {\n required: ['operationRef'],\n },\n {\n required: ['operationId'],\n },\n ],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'link-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/link',\n },\n },\n header: {\n type: 'object',\n properties: {\n description: {\n type: 'string',\n },\n required: {\n default: false,\n type: 'boolean',\n },\n deprecated: {\n default: false,\n type: 'boolean',\n },\n schema: {\n $ref: '#/$defs/schema',\n },\n content: {\n $ref: '#/$defs/content',\n },\n },\n oneOf: [\n {\n required: ['schema'],\n },\n {\n required: ['content'],\n },\n ],\n dependentSchemas: {\n schema: {\n properties: {\n style: {\n default: 'simple',\n const: 'simple',\n },\n explode: {\n default: false,\n type: 'boolean',\n },\n },\n $ref: '#/$defs/examples',\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'header-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/header',\n },\n },\n tag: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n externalDocs: {\n $ref: '#/$defs/external-documentation',\n },\n },\n required: ['name'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n reference: {\n type: 'object',\n properties: {\n $ref: {\n type: 'string',\n format: 'uri-reference',\n },\n summary: {\n type: 'string',\n },\n description: {\n type: 'string',\n },\n },\n unevaluatedProperties: false,\n },\n schema: {\n $dynamicAnchor: 'meta',\n type: ['object', 'boolean'],\n },\n 'security-scheme': {\n type: 'object',\n properties: {\n type: {\n enum: ['apiKey', 'http', 'mutualTLS', 'oauth2', 'openIdConnect'],\n },\n description: {\n type: 'string',\n },\n },\n required: ['type'],\n allOf: [\n {\n $ref: '#/$defs/specification-extensions',\n },\n {\n $ref: '#/$defs/security-scheme/$defs/type-apikey',\n },\n {\n $ref: '#/$defs/security-scheme/$defs/type-http',\n },\n {\n $ref: '#/$defs/security-scheme/$defs/type-http-bearer',\n },\n {\n $ref: '#/$defs/security-scheme/$defs/type-oauth2',\n },\n {\n $ref: '#/$defs/security-scheme/$defs/type-oidc',\n },\n ],\n unevaluatedProperties: false,\n $defs: {\n 'type-apikey': {\n if: {\n properties: {\n type: {\n const: 'apiKey',\n },\n },\n required: ['type'],\n },\n then: {\n properties: {\n name: {\n type: 'string',\n },\n in: {\n enum: ['query', 'header', 'cookie'],\n },\n },\n required: ['name', 'in'],\n },\n },\n 'type-http': {\n if: {\n properties: {\n type: {\n const: 'http',\n },\n },\n required: ['type'],\n },\n then: {\n properties: {\n scheme: {\n type: 'string',\n },\n },\n required: ['scheme'],\n },\n },\n 'type-http-bearer': {\n if: {\n properties: {\n type: {\n const: 'http',\n },\n scheme: {\n type: 'string',\n pattern: '^[Bb][Ee][Aa][Rr][Ee][Rr]$',\n },\n },\n required: ['type', 'scheme'],\n },\n then: {\n properties: {\n bearerFormat: {\n type: 'string',\n },\n },\n },\n },\n 'type-oauth2': {\n if: {\n properties: {\n type: {\n const: 'oauth2',\n },\n },\n required: ['type'],\n },\n then: {\n properties: {\n flows: {\n $ref: '#/$defs/oauth-flows',\n },\n },\n required: ['flows'],\n },\n },\n 'type-oidc': {\n if: {\n properties: {\n type: {\n const: 'openIdConnect',\n },\n },\n required: ['type'],\n },\n then: {\n properties: {\n openIdConnectUrl: {\n type: 'string',\n format: 'uri',\n },\n },\n required: ['openIdConnectUrl'],\n },\n },\n },\n },\n 'security-scheme-or-reference': {\n if: {\n type: 'object',\n required: ['$ref'],\n },\n then: {\n $ref: '#/$defs/reference',\n },\n else: {\n $ref: '#/$defs/security-scheme',\n },\n },\n 'oauth-flows': {\n type: 'object',\n properties: {\n implicit: {\n $ref: '#/$defs/oauth-flows/$defs/implicit',\n },\n password: {\n $ref: '#/$defs/oauth-flows/$defs/password',\n },\n clientCredentials: {\n $ref: '#/$defs/oauth-flows/$defs/client-credentials',\n },\n authorizationCode: {\n $ref: '#/$defs/oauth-flows/$defs/authorization-code',\n },\n },\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n $defs: {\n implicit: {\n type: 'object',\n properties: {\n authorizationUrl: {\n type: 'string',\n },\n refreshUrl: {\n type: 'string',\n },\n scopes: {\n $ref: '#/$defs/map-of-strings',\n },\n },\n required: ['authorizationUrl', 'scopes'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n password: {\n type: 'object',\n properties: {\n tokenUrl: {\n type: 'string',\n },\n refreshUrl: {\n type: 'string',\n },\n scopes: {\n $ref: '#/$defs/map-of-strings',\n },\n },\n required: ['tokenUrl', 'scopes'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'client-credentials': {\n type: 'object',\n properties: {\n tokenUrl: {\n type: 'string',\n },\n refreshUrl: {\n type: 'string',\n },\n scopes: {\n $ref: '#/$defs/map-of-strings',\n },\n },\n required: ['tokenUrl', 'scopes'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n 'authorization-code': {\n type: 'object',\n properties: {\n authorizationUrl: {\n type: 'string',\n },\n tokenUrl: {\n type: 'string',\n },\n refreshUrl: {\n type: 'string',\n },\n scopes: {\n $ref: '#/$defs/map-of-strings',\n },\n },\n required: ['authorizationUrl', 'tokenUrl', 'scopes'],\n $ref: '#/$defs/specification-extensions',\n unevaluatedProperties: false,\n },\n },\n },\n 'security-requirement': {\n type: 'object',\n additionalProperties: {\n type: 'array',\n items: {\n type: 'string',\n },\n },\n },\n 'specification-extensions': {\n patternProperties: {\n '^x-': true,\n },\n },\n examples: {\n properties: {\n example: true,\n examples: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/example-or-reference',\n },\n },\n },\n },\n 'map-of-strings': {\n type: 'object',\n additionalProperties: {\n type: 'string',\n },\n },\n },\n};\n\nconst OAS_SCHEMAS = {\n '2.0': OAS_2,\n '3.0': OAS_3,\n 3.1: OAS_3_1,\n};\n\nfunction shouldIgnoreError(error) {\n return (\n // oneOf is a fairly error as we have 2 options to choose from for most of the time.\n error.keyword === 'oneOf' ||\n // the required $ref is entirely useless, since oas-schema rules operate on resolved content, so there won't be any $refs in the document\n (error.keyword === 'required' && error.params.missingProperty === '$ref')\n );\n}\n\n// this is supposed to cover edge cases we need to cover manually, when it's impossible to detect the most appropriate error, i.e. oneOf consisting of more than 3 members, etc.\n// note, more errors can be included if certain messages reported by AJV are not quite meaningful\nconst ERROR_MAP = [\n {\n path: /^components\\/securitySchemes\\/[^/]+$/,\n message: 'Invalid security scheme',\n },\n];\n\n// The function removes irrelevant (aka misleading, confusing, useless, whatever you call it) errors.\n// There are a few exceptions, i.e. security components I covered manually,\n// yet apart from them we usually deal with a relatively simple scenario that can be literally expressed as: \"either proper value of $ref property\".\n// The $ref part is never going to be interesting for us, because both oas-schema rules operate on resolved content, so we won't have any $refs left.\n// As you can see, what we deal here wit is actually not really oneOf anymore - it's always the first member of oneOf we match against.\n// That being said, we always strip both oneOf and $ref, since we are always interested in the first error.\nexport function prepareResults(errors) {\n // Update additionalProperties errors to make them more precise and prevent them from being treated as duplicates\n for (const error of errors) {\n if (error.keyword === 'additionalProperties') {\n error.instancePath = `${error.instancePath}/${String(error.params['additionalProperty'])}`;\n }\n }\n\n for (let i = 0; i < errors.length; i++) {\n const error = errors[i];\n\n if (i + 1 < errors.length && errors[i + 1].instancePath === error.instancePath) {\n errors.splice(i + 1, 1);\n i--;\n } else if (i > 0 && shouldIgnoreError(error) && errors[i - 1].instancePath.startsWith(error.instancePath)) {\n errors.splice(i, 1);\n i--;\n }\n }\n}\n\nfunction applyManualReplacements(errors) {\n for (const error of errors) {\n if (error.path === void 0) continue;\n\n const joinedPath = error.path.join('/');\n\n for (const mappedError of ERROR_MAP) {\n if (mappedError.path.test(joinedPath)) {\n error.message = mappedError.message;\n break;\n }\n }\n }\n}\n\nexport default createRulesetFunction(\n {\n input: null,\n options: null,\n },\n function oasDocumentSchema(targetVal, opts, context) {\n const formats = context.document.formats;\n if (formats === null || formats === void 0) return;\n\n const schema = formats.has(oas2)\n ? OAS_SCHEMAS['2.0']\n : formats.has(oas3_1)\n ? OAS_SCHEMAS['3.1']\n : OAS_SCHEMAS['3.0'];\n\n const errors = schemaFn(targetVal, { allErrors: true, schema, prepareResults }, context);\n\n if (Array.isArray(errors)) {\n applyManualReplacements(errors);\n }\n\n return errors;\n },\n);\n" - }, - { - "id": "oWnSsB6ESXiPxcECo6sxv", - "extendedFrom": "", - "name": "oasDiscriminator", - "content": "function isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nexport const oasDiscriminator = (schema, _opts, { path }) => {\n /**\n * This function verifies:\n *\n * 1. The discriminator property name is defined at this schema.\n * 2. The discriminator property is in the required property list.\n */\n\n if (!isObject(schema)) return;\n\n if (typeof schema.discriminator !== 'string') return;\n\n const discriminatorName = schema.discriminator;\n\n const results = [];\n\n if (!isObject(schema.properties) || !Object.keys(schema.properties).some(k => k === discriminatorName)) {\n results.push({\n message: `The discriminator property must be defined in this schema.`,\n path: [...path, 'properties'],\n });\n }\n\n if (!Array.isArray(schema.required) || !schema.required.some(n => n === discriminatorName)) {\n results.push({\n message: `The discriminator property must be in the required property list.`,\n path: [...path, 'required'],\n });\n }\n\n return results;\n};\n\nexport default oasDiscriminator;\n" - }, - { - "id": "yV8GE2M3oZxmdMy_bselm", - "extendedFrom": "", - "name": "oasExample", - "content": "import { isPlainObject, pointerToPath } from '@stoplight/json';\nimport { createRulesetFunction } from '@stoplight/spectral-core';\nimport { oas2, oas3_1, extractDraftVersion, oas3_0 } from '@stoplight/spectral-formats';\nimport { schema as schemaFn } from '@stoplight/spectral-functions';\nimport traverse from 'json-schema-traverse';\n\nconst MEDIA_VALIDATION_ITEMS = {\n 2: [\n {\n field: 'examples',\n multiple: true,\n keyed: false,\n },\n ],\n 3: [\n {\n field: 'example',\n multiple: false,\n keyed: false,\n },\n {\n field: 'examples',\n multiple: true,\n keyed: true,\n },\n ],\n};\n\nconst SCHEMA_VALIDATION_ITEMS = {\n 2: ['example', 'x-example', 'default'],\n 3: ['example', 'default'],\n};\n\nfunction isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nfunction rewriteNullable(schema, errors) {\n for (const error of errors) {\n if (error.keyword !== 'type') continue;\n const value = getSchemaProperty(schema, error.schemaPath);\n if (isPlainObject(value) && value.nullable === true) {\n error.message += ',null';\n }\n }\n}\n\nconst visitOAS2 = schema => {\n if (schema['x-nullable'] === true) {\n schema.nullable = true;\n delete schema['x-nullable'];\n }\n};\n\nfunction getSchemaProperty(schema, schemaPath) {\n const path = pointerToPath(schemaPath);\n let value = schema;\n\n for (const fragment of path.slice(0, -1)) {\n if (!isPlainObject(value)) {\n return;\n }\n\n value = value[fragment];\n }\n\n return value;\n}\n\nconst oasSchema = createRulesetFunction(\n {\n input: null,\n options: {\n type: 'object',\n properties: {\n schema: {\n type: 'object',\n },\n },\n additionalProperties: false,\n },\n },\n function oasSchema(targetVal, opts, context) {\n const formats = context.document.formats;\n\n let { schema } = opts;\n\n let dialect = 'draft4';\n let prepareResults;\n\n if (!formats) {\n dialect = 'auto';\n } else if (formats.has(oas3_1)) {\n if (isPlainObject(context.document.data) && typeof context.document.data.jsonSchemaDialect === 'string') {\n dialect = extractDraftVersion(context.document.data.jsonSchemaDialect) ?? 'draft2020-12';\n } else {\n dialect = 'draft2020-12';\n }\n } else if (formats.has(oas3_0)) {\n prepareResults = rewriteNullable.bind(null, schema);\n } else if (formats.has(oas2)) {\n const clonedSchema = JSON.parse(JSON.stringify(schema));\n traverse(clonedSchema, visitOAS2);\n schema = clonedSchema;\n prepareResults = rewriteNullable.bind(null, clonedSchema);\n }\n\n return schemaFn(\n targetVal,\n {\n ...opts,\n schema,\n prepareResults,\n dialect,\n },\n context,\n );\n },\n);\n\nfunction* getMediaValidationItems(items, targetVal, givenPath, oasVersion) {\n for (const { field, keyed, multiple } of items) {\n if (!(field in targetVal)) {\n continue;\n }\n\n const value = targetVal[field];\n\n if (multiple) {\n if (!isObject(value)) continue;\n\n for (const exampleKey of Object.keys(value)) {\n const exampleValue = value[exampleKey];\n if (oasVersion === 3 && keyed && (!isObject(exampleValue) || 'externalValue' in exampleValue)) {\n // should be covered by oas3-examples-value-or-externalValue\n continue;\n }\n\n const targetPath = [...givenPath, field, exampleKey];\n\n if (keyed) {\n targetPath.push('value');\n }\n\n yield {\n value: keyed && isObject(exampleValue) ? exampleValue.value : exampleValue,\n path: targetPath,\n };\n }\n\n return;\n } else {\n return yield {\n value,\n path: [...givenPath, field],\n };\n }\n }\n}\n\nfunction* getSchemaValidationItems(fields, targetVal, givenPath) {\n for (const field of fields) {\n if (!(field in targetVal)) {\n continue;\n }\n\n yield {\n value: targetVal[field],\n path: [...givenPath, field],\n };\n }\n}\n\nexport default createRulesetFunction(\n {\n input: {\n type: 'object',\n },\n options: {\n type: 'object',\n properties: {\n oasVersion: {\n enum: ['2', '3'],\n },\n schemaField: {\n type: 'string',\n },\n type: {\n enum: ['media', 'schema'],\n },\n },\n additionalProperties: false,\n },\n },\n function oasExample(targetVal, opts, context) {\n const formats = context.document.formats;\n const schemaOpts = {\n schema: opts.schemaField === '$' ? targetVal : targetVal[opts.schemaField],\n };\n\n let results = void 0;\n let oasVersion = parseInt(opts.oasVersion);\n\n const validationItems =\n opts.type === 'schema'\n ? getSchemaValidationItems(SCHEMA_VALIDATION_ITEMS[oasVersion], targetVal, context.path)\n : getMediaValidationItems(MEDIA_VALIDATION_ITEMS[oasVersion], targetVal, context.path, oasVersion);\n\n if (formats?.has(oas2) && 'required' in schemaOpts.schema && typeof schemaOpts.schema.required === 'boolean') {\n schemaOpts.schema = { ...schemaOpts.schema };\n delete schemaOpts.schema.required;\n }\n\n for (const validationItem of validationItems) {\n const result = oasSchema(validationItem.value, schemaOpts, {\n ...context,\n path: validationItem.path,\n });\n\n if (Array.isArray(result)) {\n if (results === void 0) results = [];\n results.push(...result);\n }\n }\n\n return results;\n },\n);\n" - }, - { - "id": "7IM2dxxl58nqnDNos_xti", - "extendedFrom": "", - "name": "oasOpFormDataConsumeCheck", - "content": "function isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nconst validConsumeValue = /(application\\/x-www-form-urlencoded|multipart\\/form-data)/;\n\nexport const oasOpFormDataConsumeCheck = targetVal => {\n if (!isObject(targetVal)) return;\n\n const parameters = targetVal.parameters;\n const consumes = targetVal.consumes;\n\n if (!Array.isArray(parameters) || !Array.isArray(consumes)) {\n return;\n }\n\n if (parameters.some(p => isObject(p) && p.in === 'formData') && !validConsumeValue.test(consumes?.join(','))) {\n return [\n {\n message: 'Consumes must include urlencoded, multipart, or form-data media type when using formData parameter.',\n },\n ];\n }\n\n return;\n};\n\nexport default oasOpFormDataConsumeCheck;\n" - }, - { - "id": "qyreBtJPioj3lxIja57-4", - "extendedFrom": "", - "name": "oasOpIdUnique", - "content": "import { isPlainObject } from '@stoplight/json';\n\nfunction isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nconst validOperationKeys = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace'];\n\nfunction* getAllOperations(paths) {\n if (!isPlainObject(paths)) {\n return;\n }\n\n const item = {\n path: '',\n operation: '',\n value: null,\n };\n\n for (const path of Object.keys(paths)) {\n const operations = paths[path];\n if (!isPlainObject(operations)) {\n continue;\n }\n\n item.path = path;\n\n for (const operation of Object.keys(operations)) {\n if (!isPlainObject(operations[operation]) || !validOperationKeys.includes(operation)) {\n continue;\n }\n\n item.operation = operation;\n item.value = operations[operation];\n\n yield item;\n }\n }\n}\n\nexport const oasOpIdUnique = targetVal => {\n if (!isObject(targetVal) || !isObject(targetVal.paths)) return;\n\n const results = [];\n\n const { paths } = targetVal;\n\n const seenIds = [];\n\n for (const { path, operation } of getAllOperations(paths)) {\n const pathValue = paths[path];\n\n if (!isObject(pathValue)) continue;\n\n const operationValue = pathValue[operation];\n\n if (!isObject(operationValue) || !('operationId' in operationValue)) {\n continue;\n }\n\n const { operationId } = operationValue;\n\n if (seenIds.includes(operationId)) {\n results.push({\n message: 'operationId must be unique.',\n path: ['paths', path, operation, 'operationId'],\n });\n } else {\n seenIds.push(operationId);\n }\n }\n\n return results;\n};\n\nexport default oasOpIdUnique;\n" - }, - { - "id": "eBZfmdV9f3yJhywGGfP6i", - "extendedFrom": "", - "name": "oasOpParams", - "content": "function isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nfunction computeFingerprint(param) {\n return `${String(param.in)}-${String(param.name)}`;\n}\n\nexport const oasOpParams = (params, _opts, { path }) => {\n /**\n * This function verifies:\n *\n * 1. Operations must have unique `name` + `in` parameters.\n * 2. Operation cannot have both `in:body` and `in:formData` parameters\n * 3. Operation must have only one `in:body` parameter.\n */\n\n if (!Array.isArray(params)) return;\n\n if (params.length < 2) return;\n\n const results = [];\n\n const count = {\n body: [],\n formData: [],\n };\n const list = [];\n const duplicates = [];\n\n let index = -1;\n\n for (const param of params) {\n index++;\n\n if (!isObject(param)) continue;\n\n // skip params that are refs\n if ('$ref' in param) continue;\n\n // Operations must have unique `name` + `in` parameters.\n const fingerprint = computeFingerprint(param);\n if (list.includes(fingerprint)) {\n duplicates.push(index);\n } else {\n list.push(fingerprint);\n }\n\n if (typeof param.in === 'string' && param.in in count) {\n count[param.in].push(index);\n }\n }\n\n if (duplicates.length > 0) {\n for (const i of duplicates) {\n results.push({\n message: 'A parameter in this operation already exposes the same combination of \"name\" and \"in\" values.',\n path: [...path, i],\n });\n }\n }\n\n if (count.body.length > 0 && count.formData.length > 0) {\n results.push({\n message: 'Operation must not have both \"in:body\" and \"in:formData\" parameters.',\n });\n }\n\n if (count.body.length > 1) {\n for (let i = 1; i < count.body.length; i++) {\n results.push({\n message: 'Operation must not have more than a single instance of the \"in:body\" parameter.',\n path: [...path, count.body[i]],\n });\n }\n }\n\n return results;\n};\n\nexport default oasOpParams;\n" - }, - { - "id": "M8I-IiA48R9_GpRVLR2-D", - "extendedFrom": "", - "name": "oasOpSecurityDefined", - "content": "import { isPlainObject } from '@stoplight/json';\nimport { createRulesetFunction } from '@stoplight/spectral-core';\n\nfunction isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nconst validOperationKeys = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace'];\n\nfunction* getAllOperations(paths) {\n if (!isPlainObject(paths)) {\n return;\n }\n\n const item = {\n path: '',\n operation: '',\n value: null,\n };\n\n for (const path of Object.keys(paths)) {\n const operations = paths[path];\n if (!isPlainObject(operations)) {\n continue;\n }\n\n item.path = path;\n\n for (const operation of Object.keys(operations)) {\n if (!isPlainObject(operations[operation]) || !validOperationKeys.includes(operation)) {\n continue;\n }\n\n item.operation = operation;\n item.value = operations[operation];\n\n yield item;\n }\n }\n}\n\nfunction _get(value, path) {\n for (const segment of path) {\n if (!isObject(value)) {\n break;\n }\n\n value = value[segment];\n }\n\n return value;\n}\n\nexport default createRulesetFunction(\n {\n input: {\n type: 'object',\n properties: {\n paths: {\n type: 'object',\n },\n security: {\n type: 'array',\n },\n },\n },\n options: {\n type: 'object',\n properties: {\n schemesPath: {\n type: 'array',\n items: {\n type: ['string', 'number'],\n },\n },\n },\n },\n },\n function oasOpSecurityDefined(targetVal, { schemesPath }) {\n const { paths } = targetVal;\n\n const results = [];\n\n const schemes = _get(targetVal, schemesPath);\n const allDefs = isObject(schemes) ? Object.keys(schemes) : [];\n\n // Check global security requirements\n\n const { security } = targetVal;\n\n if (Array.isArray(security)) {\n for (const [index, value] of security.entries()) {\n if (!isObject(value)) {\n continue;\n }\n\n const securityKeys = Object.keys(value);\n\n for (const securityKey of securityKeys) {\n if (!allDefs.includes(securityKey)) {\n results.push({\n message: `API \"security\" values must match a scheme defined in the \"${schemesPath.join('.')}\" object.`,\n path: ['security', index, securityKey],\n });\n }\n }\n }\n }\n\n for (const { path, operation, value } of getAllOperations(paths)) {\n if (!isObject(value)) continue;\n\n const { security } = value;\n\n if (!Array.isArray(security)) {\n continue;\n }\n\n for (const [index, value] of security.entries()) {\n if (!isObject(value)) {\n continue;\n }\n\n const securityKeys = Object.keys(value);\n\n for (const securityKey of securityKeys) {\n if (!allDefs.includes(securityKey)) {\n results.push({\n message: `Operation \"security\" values must match a scheme defined in the \"${schemesPath.join(\n '.',\n )}\" object.`,\n path: ['paths', path, operation, 'security', index, securityKey],\n });\n }\n }\n }\n }\n\n return results;\n },\n);\n" - }, - { - "id": "UZ4xjIYz5GYPDsvg8OcM8", - "extendedFrom": "", - "name": "oasOpSuccessResponse", - "content": "import { createRulesetFunction } from '@stoplight/spectral-core';\nimport { oas3 } from '@stoplight/spectral-formats';\n\nexport const oasOpSuccessResponse = createRulesetFunction(\n {\n input: {\n type: 'object',\n },\n options: null,\n },\n (input, opts, context) => {\n const isOAS3X = context.document.formats?.has(oas3) === true;\n\n for (const response of Object.keys(input)) {\n if (isOAS3X && (response === '2XX' || response === '3XX')) {\n return;\n }\n\n if (Number(response) >= 200 && Number(response) < 400) {\n return;\n }\n }\n\n return [\n {\n message: 'Operation must define at least a single 2xx or 3xx response',\n },\n ];\n },\n);\n\nexport default oasOpSuccessResponse;\n" - }, - { - "id": "p5iMKUsFH7_JgSeDnWdXt", - "extendedFrom": "", - "name": "oasPathParam", - "content": "function isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nconst pathRegex = /(\\{;?\\??[a-zA-Z0-9_-]+\\*?\\})/g;\n\nconst isNamedPathParam = p => {\n return p.in !== void 0 && p.in === 'path' && p.name !== void 0;\n};\n\nconst isUnknownNamedPathParam = (p, path, results, seen) => {\n if (!isNamedPathParam(p)) {\n return false;\n }\n\n if (p.required !== true) {\n results.push(generateResult(requiredMessage(p.name), path));\n }\n\n if (p.name in seen) {\n results.push(generateResult(uniqueDefinitionMessage(p.name), path));\n return false;\n }\n\n return true;\n};\n\nconst ensureAllDefinedPathParamsAreUsedInPath = (path, params, expected, results) => {\n for (const p of Object.keys(params)) {\n if (!params[p]) {\n continue;\n }\n\n if (!expected.includes(p)) {\n const resPath = params[p];\n results.push(generateResult(`Parameter \"${p}\" must be used in path \"${path}\".`, resPath));\n }\n }\n};\n\nconst ensureAllExpectedParamsInPathAreDefined = (path, params, expected, operationPath, results) => {\n for (const p of expected) {\n if (!(p in params)) {\n results.push(\n generateResult(`Operation must define parameter \"{${p}}\" as expected by path \"${path}\".`, operationPath),\n );\n }\n }\n};\n\nexport const oasPathParam = targetVal => {\n /**\n * This rule verifies:\n *\n * 1. for every param referenced in the path string ie /users/{userId}, var must be defined in either\n * path.parameters, or operation.parameters object\n * 2. every path.parameters + operation.parameters property must be used in the path string\n */\n\n if (!isObject(targetVal) || !isObject(targetVal.paths)) {\n return;\n }\n\n const results = [];\n\n // keep track of normalized paths for verifying paths are unique\n const uniquePaths = {};\n const validOperationKeys = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace'];\n\n for (const path of Object.keys(targetVal.paths)) {\n const pathValue = targetVal.paths[path];\n if (!isObject(pathValue)) continue;\n\n // verify normalized paths are functionally unique (ie `/path/{one}` vs `/path/{two}` are\n // different but equivalent within the context of OAS)\n const normalized = path.replace(pathRegex, '%'); // '%' is used here since its invalid in paths\n if (normalized in uniquePaths) {\n results.push(\n generateResult(`Paths \"${String(uniquePaths[normalized])}\" and \"${path}\" must not be equivalent.`, [\n 'paths',\n path,\n ]),\n );\n } else {\n uniquePaths[normalized] = path;\n }\n\n // find all templated path parameters\n const pathElements = [];\n let match;\n\n while ((match = pathRegex.exec(path))) {\n const p = match[0].replace(/[{}?*;]/g, '');\n if (pathElements.includes(p)) {\n results.push(generateResult(`Path \"${path}\" must not use parameter \"{${p}}\" multiple times.`, ['paths', path]));\n } else {\n pathElements.push(p);\n }\n }\n\n // find parameters set within the top-level 'parameters' object\n const topParams = {};\n if (Array.isArray(pathValue.parameters)) {\n for (const [i, value] of pathValue.parameters.entries()) {\n if (!isObject(value)) continue;\n\n const fullParameterPath = ['paths', path, 'parameters', i];\n\n if (isUnknownNamedPathParam(value, fullParameterPath, results, topParams)) {\n topParams[value.name] = fullParameterPath;\n }\n }\n }\n\n if (isObject(targetVal.paths[path])) {\n // find parameters set within the operation's 'parameters' object\n for (const op of Object.keys(pathValue)) {\n const operationValue = pathValue[op];\n if (!isObject(operationValue)) continue;\n\n if (op === 'parameters' || !validOperationKeys.includes(op)) {\n continue;\n }\n\n const operationParams = {};\n const { parameters } = operationValue;\n const operationPath = ['paths', path, op];\n\n if (Array.isArray(parameters)) {\n for (const [i, p] of parameters.entries()) {\n if (!isObject(p)) continue;\n\n const fullParameterPath = [...operationPath, 'parameters', i];\n\n if (isUnknownNamedPathParam(p, fullParameterPath, results, operationParams)) {\n operationParams[p.name] = fullParameterPath;\n }\n }\n }\n\n const definedParams = { ...topParams, ...operationParams };\n ensureAllDefinedPathParamsAreUsedInPath(path, definedParams, pathElements, results);\n ensureAllExpectedParamsInPathAreDefined(path, definedParams, pathElements, operationPath, results);\n }\n }\n }\n\n return results;\n};\n\nfunction generateResult(message, path) {\n return {\n message,\n path,\n };\n}\n\nconst requiredMessage = name => `Path parameter \"${name}\" must have \"required\" property that is set to \"true\".`;\n\nconst uniqueDefinitionMessage = name => `Path parameter \"${name}\" must not be defined multiple times.`;\n\nexport default oasPathParam;\n" - }, - { - "id": "jSPbiQ4Np9h0tZTAVxshi", - "extendedFrom": "", - "name": "oasSchema", - "content": "import traverse from 'json-schema-traverse';\nimport { schema as schemaFn } from '@stoplight/spectral-functions';\nimport { createRulesetFunction } from '@stoplight/spectral-core';\nimport { oas2, oas3_1, extractDraftVersion, oas3_0 } from '@stoplight/spectral-formats';\nimport { isPlainObject, pointerToPath } from '@stoplight/json';\n\nfunction rewriteNullable(schema, errors) {\n for (const error of errors) {\n if (error.keyword !== 'type') continue;\n const value = getSchemaProperty(schema, error.schemaPath);\n if (isPlainObject(value) && value.nullable === true) {\n error.message += ',null';\n }\n }\n}\n\nexport default createRulesetFunction(\n {\n input: null,\n options: {\n type: 'object',\n properties: {\n schema: {\n type: 'object',\n },\n },\n additionalProperties: false,\n },\n },\n function oasSchema(targetVal, opts, context) {\n const formats = context.document.formats;\n\n let { schema } = opts;\n\n let dialect = 'draft4';\n let prepareResults;\n\n if (!formats) {\n dialect = 'auto';\n } else if (formats.has(oas3_1)) {\n if (isPlainObject(context.document.data) && typeof context.document.data.jsonSchemaDialect === 'string') {\n dialect = extractDraftVersion(context.document.data.jsonSchemaDialect) ?? 'draft2020-12';\n } else {\n dialect = 'draft2020-12';\n }\n } else if (formats.has(oas3_0)) {\n prepareResults = rewriteNullable.bind(null, schema);\n } else if (formats.has(oas2)) {\n const clonedSchema = JSON.parse(JSON.stringify(schema));\n traverse(clonedSchema, visitOAS2);\n schema = clonedSchema;\n prepareResults = rewriteNullable.bind(null, clonedSchema);\n }\n\n return schemaFn(\n targetVal,\n {\n ...opts,\n schema,\n prepareResults,\n dialect,\n },\n context,\n );\n },\n);\n\nconst visitOAS2 = schema => {\n if (schema['x-nullable'] === true) {\n schema.nullable = true;\n delete schema['x-nullable'];\n }\n};\n\nfunction getSchemaProperty(schema, schemaPath) {\n const path = pointerToPath(schemaPath);\n let value = schema;\n\n for (const fragment of path.slice(0, -1)) {\n if (!isPlainObject(value)) {\n return;\n }\n\n value = value[fragment];\n }\n\n return value;\n}\n" - }, - { - "id": "zkdTymCWqcmxPJWZxyMxr", - "extendedFrom": "", - "name": "oasTagDefined", - "content": "// This function will check an API doc to verify that any tag that appears on\n// an operation is also present in the global tags array.\nimport { isPlainObject } from '@stoplight/json';\n\nfunction isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nconst validOperationKeys = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace'];\n\nfunction* getAllOperations(paths) {\n if (!isPlainObject(paths)) {\n return;\n }\n\n const item = {\n path: '',\n operation: '',\n value: null,\n };\n\n for (const path of Object.keys(paths)) {\n const operations = paths[path];\n if (!isPlainObject(operations)) {\n continue;\n }\n\n item.path = path;\n\n for (const operation of Object.keys(operations)) {\n if (!isPlainObject(operations[operation]) || !validOperationKeys.includes(operation)) {\n continue;\n }\n\n item.operation = operation;\n item.value = operations[operation];\n\n yield item;\n }\n }\n}\n\nexport const oasTagDefined = targetVal => {\n if (!isObject(targetVal)) return;\n const results = [];\n\n const globalTags = [];\n\n if (Array.isArray(targetVal.tags)) {\n for (const tag of targetVal.tags) {\n if (isObject(tag) && typeof tag.name === 'string') {\n globalTags.push(tag.name);\n }\n }\n }\n\n const { paths } = targetVal;\n\n for (const { path, operation, value } of getAllOperations(paths)) {\n if (!isObject(value)) continue;\n\n const { tags } = value;\n\n if (!Array.isArray(tags)) {\n continue;\n }\n\n for (const [i, tag] of tags.entries()) {\n if (!globalTags.includes(tag)) {\n results.push({\n message: 'Operation tags must be defined in global tags.',\n path: ['paths', path, operation, 'tags', i],\n });\n }\n }\n }\n\n return results;\n};\n\nexport default oasTagDefined;\n" - }, - { - "id": "bSNTJ6RVt78jw4E_B8RZx", - "extendedFrom": "", - "name": "oasUnusedComponent", - "content": "import { unreferencedReusableObject } from '@stoplight/spectral-functions';\nimport { createRulesetFunction } from '@stoplight/spectral-core';\n\nfunction isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nexport default createRulesetFunction(\n {\n input: {\n type: 'object',\n properties: {\n components: {\n type: 'object',\n },\n },\n required: ['components'],\n },\n options: null,\n },\n function oasUnusedComponent(targetVal, opts, context) {\n const results = [];\n const componentTypes = [\n 'schemas',\n 'responses',\n 'parameters',\n 'examples',\n 'requestBodies',\n 'headers',\n 'links',\n 'callbacks',\n ];\n\n for (const type of componentTypes) {\n const value = targetVal.components[type];\n if (!isObject(value)) continue;\n\n const resultsForType = unreferencedReusableObject(\n value,\n { reusableObjectsLocation: `#/components/${type}` },\n context,\n );\n if (resultsForType !== void 0 && Array.isArray(resultsForType)) {\n results.push(...resultsForType);\n }\n }\n\n return results;\n },\n);\n" - }, - { - "id": "-uVRn9J4Aa9vPBuXTKs3j", - "extendedFrom": "", - "name": "refSiblings", - "content": "function isObject(value) {\n return value !== null && typeof value === 'object';\n}\n\nfunction getParentValue(document, path) {\n if (path.length === 0) {\n return null;\n }\n\n let piece = document;\n\n for (let i = 0; i < path.length - 1; i += 1) {\n if (!isObject(piece)) {\n return null;\n }\n\n piece = piece[path[i]];\n }\n\n return piece;\n}\n\nconst refSiblings = (targetVal, opts, { document, path }) => {\n const value = getParentValue(document.data, path);\n\n if (!isObject(value)) {\n return;\n }\n\n const keys = Object.keys(value);\n if (keys.length === 1) {\n return;\n }\n\n const results = [];\n const actualObjPath = path.slice(0, -1);\n\n for (const key of keys) {\n if (key === '$ref') {\n continue;\n }\n results.push({\n message: '$ref must not be placed next to any other properties',\n path: [...actualObjPath, key],\n });\n }\n\n return results;\n};\n\nexport default refSiblings;\n" - }, - { - "id": "JLGe8QHPvzG6Ov6FWmWFZ", - "extendedFrom": "", - "name": "typedEnum", - "content": "import { oas2, oas3_0 } from '@stoplight/spectral-formats';\nimport { printValue } from '@stoplight/spectral-runtime';\nimport { createRulesetFunction } from '@stoplight/spectral-core';\n\nfunction getDataType(input, checkForInteger) {\n const type = typeof input;\n switch (type) {\n case 'string':\n case 'boolean':\n return type;\n case 'number':\n if (checkForInteger && Number.isInteger(input)) {\n return 'integer';\n }\n\n return 'number';\n case 'object':\n if (input === null) {\n return 'null';\n }\n\n return Array.isArray(input) ? 'array' : 'object';\n default:\n throw TypeError('Unknown input type');\n }\n}\n\nfunction getTypes(input, formats) {\n const { type } = input;\n\n if (\n (input.nullable === true && formats?.has(oas3_0) === true) ||\n (input['x-nullable'] === true && formats?.has(oas2) === true)\n ) {\n return Array.isArray(type) ? [...type, 'null'] : [type, 'null'];\n }\n\n return type;\n}\n\nexport const typedEnum = createRulesetFunction(\n {\n input: {\n type: 'object',\n properties: {\n enum: {\n type: 'array',\n },\n type: {\n oneOf: [\n {\n type: 'array',\n items: {\n type: 'string',\n },\n },\n {\n type: 'string',\n },\n ],\n },\n },\n required: ['enum', 'type'],\n },\n options: null,\n },\n function (input, opts, context) {\n const { enum: enumValues } = input;\n const type = getTypes(input, context.document.formats);\n const checkForInteger = type === 'integer' || (Array.isArray(type) && type.includes('integer'));\n\n let results;\n\n enumValues.forEach((value, i) => {\n const valueType = getDataType(value, checkForInteger);\n\n if (valueType === type || (Array.isArray(type) && type.includes(valueType))) {\n return;\n }\n\n results ??= [];\n results.push({\n message: `Enum value ${printValue(enumValues[i])} must be \"${String(type)}\".`,\n path: [...context.path, 'enum', i],\n });\n });\n\n return results;\n },\n);\n\nexport default typedEnum;\n" - } - ], - "extendedDefault": false - } - }, - "extendedDefault": true -} \ No newline at end of file diff --git a/Brewfile b/Brewfile new file mode 100644 index 00000000..e4feee60 --- /dev/null +++ b/Brewfile @@ -0,0 +1 @@ +brew "node" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..8c3a56ff --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,75 @@ +# Changelog + +## 0.1.0-alpha.1 (2025-11-24) + +Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/Terminal49/API/compare/v0.0.1-alpha.0...v0.1.0-alpha.1) + +### Features + +* **mcp:** enable optional code execution tool on http mcp servers ([b64c4e9](https://github.com/Terminal49/API/commit/b64c4e98c96aeec3046e5dc79c193077f38bb31c)) +* routing endpoints ([#137](https://github.com/Terminal49/API/issues/137)) ([444d73a](https://github.com/Terminal49/API/commit/444d73abf796697e584ffea492f6a144301063ff)) +* Update API documentation with consistent code style ([6feca12](https://github.com/Terminal49/API/commit/6feca125ec4763ef28050e410d2dba7723a05553)) + + +### Bug Fixes + +* **mcpb:** pin @anthropic-ai/mcpb version ([b0b95f5](https://github.com/Terminal49/API/commit/b0b95f552aca514f696f4784a20a5f494bef6f4c)) +* **mcp:** fix cli argument parsing logic ([ede6ca2](https://github.com/Terminal49/API/commit/ede6ca26441a7d523370ff3acc9e2f72d0333e60)) +* **mcp:** resolve a linting issue in server code ([b15b8ce](https://github.com/Terminal49/API/commit/b15b8ce7037168f1523d0edb8863721f393b2aa0)) +* **mcp:** return tool execution error on jq failure ([fec4eb2](https://github.com/Terminal49/API/commit/fec4eb2d1c0527ab9c7bbb389f1c97d689fbe9e5)) + + +### Performance Improvements + +* faster formatting ([0f63a87](https://github.com/Terminal49/API/commit/0f63a87f08127ec6e4d7af0c387d00cbff60cfdb)) + + +### Chores + +* Auto-generate Postman collection from openapi.json [skip ci] ([0014457](https://github.com/Terminal49/API/commit/0014457552587843b354cefb8a47946624265590)) +* Auto-generate Postman collection from openapi.json [skip ci] ([2de5f6d](https://github.com/Terminal49/API/commit/2de5f6d1276c225013633b433912c28c115f9c53)) +* Auto-generate Postman collection from openapi.json [skip ci] ([b187f8b](https://github.com/Terminal49/API/commit/b187f8b0cac20c15c97ddaefbae0ff796636d45e)) +* Auto-generate Postman collection from openapi.json [skip ci] ([ee25375](https://github.com/Terminal49/API/commit/ee253755972153085cd4cd8322de09302b5d7135)) +* Auto-generate Postman collection from openapi.json [skip ci] ([dfdc873](https://github.com/Terminal49/API/commit/dfdc8739fd0f7fc5326a20815c617b5b51aece4e)) +* Auto-generate Postman collection from openapi.json [skip ci] ([3c2edec](https://github.com/Terminal49/API/commit/3c2edecc7b379952f5c254ae21d4f201efbb2c8d)) +* consistent documentation in define Authorization token ([41e65e3](https://github.com/Terminal49/API/commit/41e65e3beb34758da5f542182ed95c8deed0b413)) +* **docs:** add Kimmie's suggestions ([24c8beb](https://github.com/Terminal49/API/commit/24c8bebd1657cbba12e2cd601d6ac3ebcb9b83f7)) +* **docs:** add map styling guide ([2d652db](https://github.com/Terminal49/API/commit/2d652dba04ccd4cbf002aa4a46a5c39fe12cb6e2)) +* **docs:** embedding the map ([d5ee3c5](https://github.com/Terminal49/API/commit/d5ee3c58b165dda56e39c61f1a65ec60f48645d7)) +* **docs:** remove FAQ ([27f7446](https://github.com/Terminal49/API/commit/27f7446d93e57f1836778f00bb9738d8090020d8)) +* **docs:** remove frontmatter ([829a486](https://github.com/Terminal49/API/commit/829a48691d2ad4e7c120a63f9aafbe738e15d104)) +* **docs:** tell users to reach out to support for a publishable api key ([275c651](https://github.com/Terminal49/API/commit/275c6517db6eed686ea7888271918db0f3aba4bb)) +* extract some types in mcp docs ([c401b96](https://github.com/Terminal49/API/commit/c401b96fdfd08cc3875b4c70ad2b301449dca2d5)) +* **internal:** codegen related update ([497d3bf](https://github.com/Terminal49/API/commit/497d3bfaad91d6914debe53f26f34ef9b1c4d84b)) +* **internal:** codegen related update ([ce47236](https://github.com/Terminal49/API/commit/ce47236fdb64f3d1c794c98c739f604ecf9c8111)) +* **internal:** codegen related update ([f84ecdf](https://github.com/Terminal49/API/commit/f84ecdfd90a15a670b3cf8d485065ff2d3c0304f)) +* **internal:** fix incremental formatting in some cases ([38dfa5d](https://github.com/Terminal49/API/commit/38dfa5dc6ada0c4001dc91045d604413549cb3d5)) +* **internal:** grammar fix (it's -> its) ([6f50c26](https://github.com/Terminal49/API/commit/6f50c26b5c5f2091546aab45c975504577c4ddbc)) +* **internal:** ignore .eslintcache ([a3a91d5](https://github.com/Terminal49/API/commit/a3a91d59e6bbc6cc72cc77adac8d561f9f4367b7)) +* **internal:** remove .eslintcache ([4c240a6](https://github.com/Terminal49/API/commit/4c240a69ab7742cc3394696f8487e90ac123f083)) +* **internal:** remove deprecated `compilerOptions.baseUrl` from tsconfig.json ([2dc6c24](https://github.com/Terminal49/API/commit/2dc6c2440b784f44084d504f23b61c039af7795d)) +* **internal:** use npm pack for build uploads ([9053cb7](https://github.com/Terminal49/API/commit/9053cb7bb1f3ac4c1d9f476d182532996b389e6b)) +* **jsdoc:** fix [@link](https://github.com/link) annotations to refer only to parts of the package‘s public interface ([30fcefa](https://github.com/Terminal49/API/commit/30fcefabf83c22664ac648458470b777731336f6)) +* mcp code tool explicit error message when missing a run function ([e1ee122](https://github.com/Terminal49/API/commit/e1ee12271397010193b4e466e49ae03e3e64e8e4)) +* **mcp:** add friendlier MCP code tool errors on incorrect method invocations ([eb5b0fb](https://github.com/Terminal49/API/commit/eb5b0fbfa66e64da691c39ff265e7368ca9cf301)) +* **mcp:** add line numbers to code tool errors ([338b1a5](https://github.com/Terminal49/API/commit/338b1a57b8c80ba6e91e3e885f04dc62dd79a537)) +* **mcp:** allow pointing `docs_search` tool at other URLs ([7203f41](https://github.com/Terminal49/API/commit/7203f410bdf0d56012c2682d59f7c52c9942fcdf)) +* **mcp:** clarify http auth error ([b2ab851](https://github.com/Terminal49/API/commit/b2ab851e83e777cfde20bc441d897c57c927668a)) +* **mcp:** upgrade jq-web ([b4b3ebc](https://github.com/Terminal49/API/commit/b4b3ebc63037fa340491805a0191f0677fe23870)) +* sync repo ([d6d221b](https://github.com/Terminal49/API/commit/d6d221bcb9e5a3de064d488a2bd2fe75da555592)) +* Update authentication header in API documentation ([e5a16d8](https://github.com/Terminal49/API/commit/e5a16d8b33db6092dadb62240320fc296d129f10)) +* Update links in documentation to use relative paths ([787edf5](https://github.com/Terminal49/API/commit/787edf58c0c9be31df42ab662b0107187a0964af)) +* update lockfile ([8fe064e](https://github.com/Terminal49/API/commit/8fe064e19abfcc0bfd54b027a80a73bbaeac118f)) +* update old documentation links to be relatives ([b2d37fa](https://github.com/Terminal49/API/commit/b2d37fa67415ad02b3ed0f8c04ec25506a82baff)) +* update SDK settings ([18778c3](https://github.com/Terminal49/API/commit/18778c3ff151d9bd7c0ce0b7667242df722b2fa0)) +* use structured error when code execution tool errors ([aadf506](https://github.com/Terminal49/API/commit/aadf506277ad7c44d917aadd061bf1559e78c3b6)) + + +### Documentation + +* fix relative paths removing "/docs" ([17234ba](https://github.com/Terminal49/API/commit/17234ba07b738aa8d62040189c51536477bcc9ae)) + + +### Refactors + +* **docs:** rename assets to terminal49-map ([a3304fc](https://github.com/Terminal49/API/commit/a3304fc79eda7fe58852076d0f5d1deaf2acde3b)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..e26caf49 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,93 @@ +## Setting up the environment + +This repository uses [`yarn@v1`](https://classic.yarnpkg.com/lang/en/docs/install). +Other package managers may work but are not officially supported for development. + +To set up the repository, run: + +```sh +$ yarn +$ yarn build +``` + +This will install all the required dependencies and build output files to `dist/`. + +## Modifying/Adding code + +Most of the SDK is generated code. Modifications to code will be persisted between generations, but may +result in merge conflicts between manual patches and changes from the generator. The generator will never +modify the contents of the `src/lib/` and `examples/` directories. + +## Adding and running examples + +All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. + +```ts +// add an example to examples/.ts + +#!/usr/bin/env -S npm run tsn -T +… +``` + +```sh +$ chmod +x examples/.ts +# run the example against your api +$ yarn tsn -T examples/.ts +``` + +## Using the repository from source + +If you’d like to use the repository from source, you can either install from git or link to a cloned repository: + +To install via git: + +```sh +$ npm install git+ssh://git@github.com:Terminal49/API.git +``` + +Alternatively, to link a local copy of the repo: + +```sh +# Clone +$ git clone https://www.github.com/Terminal49/API +$ cd API + +# With yarn +$ yarn link +$ cd ../my-package +$ yarn link terminal49 + +# With pnpm +$ pnpm link --global +$ cd ../my-package +$ pnpm link -—global terminal49 +``` + +## Running tests + +Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. + +```sh +$ npx prism mock path/to/your/openapi.yml +``` + +```sh +$ yarn run test +``` + +## Linting and formatting + +This repository uses [prettier](https://www.npmjs.com/package/prettier) and +[eslint](https://www.npmjs.com/package/eslint) to format the code in the repository. + +To lint: + +```sh +$ yarn lint +``` + +To format and fix all lint issues automatically: + +```sh +$ yarn fix +``` diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index b2c69ed8..00000000 --- a/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM redocly/redoc -COPY nginx.conf /etc/nginx/ -COPY assets/images /usr/share/nginx/html/assets/images -COPY reference/terminal49/terminal49.v1.json /usr/share/nginx/html/terminal49.v1.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..74f0b94b --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 Terminal49 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 00000000..68351b3c --- /dev/null +++ b/README.md @@ -0,0 +1,361 @@ +# Terminal49 TypeScript API Library + +[![NPM version]()](https://npmjs.org/package/terminal49) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/terminal49) + +This library provides convenient access to the Terminal49 REST API from server-side TypeScript or JavaScript. + +The REST API documentation can be found on [www.terminal49.com](https://www.terminal49.com). The full API of this library can be found in [api.md](api.md). + +It is generated with [Stainless](https://www.stainless.com/). + +## Installation + +```sh +npm install git+ssh://git@github.com:Terminal49/API.git +``` + +> [!NOTE] +> Once this package is [published to npm](https://www.stainless.com/docs/guides/publish), this will become: `npm install terminal49` + +## Usage + +The full API of this library can be found in [api.md](api.md). + + +```js +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + apiKey: process.env['TERMINAL49_API_KEY'], // This is the default and can be omitted +}); + +const shipments = await client.shipments.list(); + +console.log(shipments.data); +``` + +### Request & Response types + +This library includes TypeScript definitions for all request params and response fields. You may import and use them like so: + + +```ts +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + apiKey: process.env['TERMINAL49_API_KEY'], // This is the default and can be omitted +}); + +const shipments: Terminal49.ShipmentListResponse = await client.shipments.list(); +``` + +Documentation for each method, request param, and response field are available in docstrings and will appear on hover in most modern editors. + +## Handling errors + +When the library is unable to connect to the API, +or if the API returns a non-success status code (i.e., 4xx or 5xx response), +a subclass of `APIError` will be thrown: + + +```ts +const shipments = await client.shipments.list().catch(async (err) => { + if (err instanceof Terminal49.APIError) { + console.log(err.status); // 400 + console.log(err.name); // BadRequestError + console.log(err.headers); // {server: 'nginx', ...} + } else { + throw err; + } +}); +``` + +Error codes are as follows: + +| Status Code | Error Type | +| ----------- | -------------------------- | +| 400 | `BadRequestError` | +| 401 | `AuthenticationError` | +| 403 | `PermissionDeniedError` | +| 404 | `NotFoundError` | +| 422 | `UnprocessableEntityError` | +| 429 | `RateLimitError` | +| >=500 | `InternalServerError` | +| N/A | `APIConnectionError` | + +### Retries + +Certain errors will be automatically retried 2 times by default, with a short exponential backoff. +Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, +429 Rate Limit, and >=500 Internal errors will all be retried by default. + +You can use the `maxRetries` option to configure or disable this: + + +```js +// Configure the default for all requests: +const client = new Terminal49({ + maxRetries: 0, // default is 2 +}); + +// Or, configure per-request: +await client.shipments.list({ + maxRetries: 5, +}); +``` + +### Timeouts + +Requests time out after 1 minute by default. You can configure this with a `timeout` option: + + +```ts +// Configure the default for all requests: +const client = new Terminal49({ + timeout: 20 * 1000, // 20 seconds (default is 1 minute) +}); + +// Override per-request: +await client.shipments.list({ + timeout: 5 * 1000, +}); +``` + +On timeout, an `APIConnectionTimeoutError` is thrown. + +Note that requests which time out will be [retried twice by default](#retries). + +## Advanced Usage + +### Accessing raw Response data (e.g., headers) + +The "raw" `Response` returned by `fetch()` can be accessed through the `.asResponse()` method on the `APIPromise` type that all methods return. +This method returns as soon as the headers for a successful response are received and does not consume the response body, so you are free to write custom parsing or streaming logic. + +You can also use the `.withResponse()` method to get the raw `Response` along with the parsed data. +Unlike `.asResponse()` this method consumes the body, returning once it is parsed. + + +```ts +const client = new Terminal49(); + +const response = await client.shipments.list().asResponse(); +console.log(response.headers.get('X-My-Header')); +console.log(response.statusText); // access the underlying Response object + +const { data: shipments, response: raw } = await client.shipments.list().withResponse(); +console.log(raw.headers.get('X-My-Header')); +console.log(shipments.data); +``` + +### Logging + +> [!IMPORTANT] +> All log messages are intended for debugging only. The format and content of log messages +> may change between releases. + +#### Log levels + +The log level can be configured in two ways: + +1. Via the `TERMINAL49_LOG` environment variable +2. Using the `logLevel` client option (overrides the environment variable if set) + +```ts +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + logLevel: 'debug', // Show all log messages +}); +``` + +Available log levels, from most to least verbose: + +- `'debug'` - Show debug messages, info, warnings, and errors +- `'info'` - Show info messages, warnings, and errors +- `'warn'` - Show warnings and errors (default) +- `'error'` - Show only errors +- `'off'` - Disable all logging + +At the `'debug'` level, all HTTP requests and responses are logged, including headers and bodies. +Some authentication-related headers are redacted, but sensitive data in request and response bodies +may still be visible. + +#### Custom logger + +By default, this library logs to `globalThis.console`. You can also provide a custom logger. +Most logging libraries are supported, including [pino](https://www.npmjs.com/package/pino), [winston](https://www.npmjs.com/package/winston), [bunyan](https://www.npmjs.com/package/bunyan), [consola](https://www.npmjs.com/package/consola), [signale](https://www.npmjs.com/package/signale), and [@std/log](https://jsr.io/@std/log). If your logger doesn't work, please open an issue. + +When providing a custom logger, the `logLevel` option still controls which messages are emitted, messages +below the configured level will not be sent to your logger. + +```ts +import Terminal49 from 'terminal49'; +import pino from 'pino'; + +const logger = pino(); + +const client = new Terminal49({ + logger: logger.child({ name: 'Terminal49' }), + logLevel: 'debug', // Send all messages to pino, allowing it to filter +}); +``` + +### Making custom/undocumented requests + +This library is typed for convenient access to the documented API. If you need to access undocumented +endpoints, params, or response properties, the library can still be used. + +#### Undocumented endpoints + +To make requests to undocumented endpoints, you can use `client.get`, `client.post`, and other HTTP verbs. +Options on the client, such as retries, will be respected when making these requests. + +```ts +await client.post('/some/path', { + body: { some_prop: 'foo' }, + query: { some_query_arg: 'bar' }, +}); +``` + +#### Undocumented request params + +To make requests using undocumented parameters, you may use `// @ts-expect-error` on the undocumented +parameter. This library doesn't validate at runtime that the request matches the type, so any extra values you +send will be sent as-is. + +```ts +client.shipments.list({ + // ... + // @ts-expect-error baz is not yet public + baz: 'undocumented option', +}); +``` + +For requests with the `GET` verb, any extra params will be in the query, all other requests will send the +extra param in the body. + +If you want to explicitly send an extra argument, you can do so with the `query`, `body`, and `headers` request +options. + +#### Undocumented response properties + +To access undocumented response properties, you may access the response object with `// @ts-expect-error` on +the response object, or cast the response object to the requisite type. Like the request params, we do not +validate or strip extra properties from the response from the API. + +### Customizing the fetch client + +By default, this library expects a global `fetch` function is defined. + +If you want to use a different `fetch` function, you can either polyfill the global: + +```ts +import fetch from 'my-fetch'; + +globalThis.fetch = fetch; +``` + +Or pass it to the client: + +```ts +import Terminal49 from 'terminal49'; +import fetch from 'my-fetch'; + +const client = new Terminal49({ fetch }); +``` + +### Fetch options + +If you want to set custom `fetch` options without overriding the `fetch` function, you can provide a `fetchOptions` object when instantiating the client or making a request. (Request-specific options override client options.) + +```ts +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + fetchOptions: { + // `RequestInit` options + }, +}); +``` + +#### Configuring proxies + +To modify proxy behavior, you can provide custom `fetchOptions` that add runtime-specific proxy +options to requests: + + **Node** [[docs](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md#example---proxyagent-with-fetch)] + +```ts +import Terminal49 from 'terminal49'; +import * as undici from 'undici'; + +const proxyAgent = new undici.ProxyAgent('http://localhost:8888'); +const client = new Terminal49({ + fetchOptions: { + dispatcher: proxyAgent, + }, +}); +``` + + **Bun** [[docs](https://bun.sh/guides/http/proxy)] + +```ts +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + fetchOptions: { + proxy: 'http://localhost:8888', + }, +}); +``` + + **Deno** [[docs](https://docs.deno.com/api/deno/~/Deno.createHttpClient)] + +```ts +import Terminal49 from 'npm:terminal49'; + +const httpClient = Deno.createHttpClient({ proxy: { url: 'http://localhost:8888' } }); +const client = new Terminal49({ + fetchOptions: { + client: httpClient, + }, +}); +``` + +## Frequently Asked Questions + +## Semantic versioning + +This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: + +1. Changes that only affect static types, without breaking runtime behavior. +2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ +3. Changes that we do not expect to impact the vast majority of users in practice. + +We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. + +We are keen for your feedback; please open an [issue](https://www.github.com/Terminal49/API/issues) with questions, bugs, or suggestions. + +## Requirements + +TypeScript >= 4.9 is supported. + +The following runtimes are supported: + +- Web browsers (Up-to-date Chrome, Firefox, Safari, Edge, and more) +- Node.js 20 LTS or later ([non-EOL](https://endoflife.date/nodejs)) versions. +- Deno v1.28.0 or higher. +- Bun 1.0 or later. +- Cloudflare Workers. +- Vercel Edge Runtime. +- Jest 28 or greater with the `"node"` environment (`"jsdom"` is not supported at this time). +- Nitro v2.6 or greater. + +Note that React Native is not supported at this time. + +If you are interested in other runtime environments, please open or upvote an issue on GitHub. + +## Contributing + +See [the contributing documentation](./CONTRIBUTING.md). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..62f90b05 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,27 @@ +# Security Policy + +## Reporting Security Issues + +This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. + +To report a security issue, please contact the Stainless team at security@stainless.com. + +## Responsible Disclosure + +We appreciate the efforts of security researchers and individuals who help us maintain the security of +SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible +disclosure practices by allowing us a reasonable amount of time to investigate and address the issue +before making any information public. + +## Reporting Non-SDK Related Security Issues + +If you encounter security issues that are not directly related to SDKs but pertain to the services +or products provided by Terminal49, please follow the respective company's security reporting guidelines. + +### Terminal49 Terms and Policies + +Please contact support@terminal49.com for any questions or concerns regarding the security of our services. + +--- + +Thank you for helping us keep the SDKs and systems they interact with secure. diff --git a/Terminal49 Shiping Line Support.csv b/Terminal49 Shiping Line Support.csv deleted file mode 100644 index aba38a03..00000000 --- a/Terminal49 Shiping Line Support.csv +++ /dev/null @@ -1,23 +0,0 @@ -Full Name,Nickname,SCAC,BOL Prefix,Support,Notes -American President Lines,APL,APLU,APLU,Yes, -Australia National Line,ANL,ANNU,ANLC,Yes, -China Ocean Shipping Company,COSCO,COSU,CCMJ,Yes, -CMA CGM,CMA CGM,CMDU,CMDU,Yes, -Evergreen,Evergreen,EGLV,EISU,Yes, -Hamburg Sud,Hamburg Sud,SUDU,SUDU,Yes, -Hapag-Lloyd,Hapag-Lloyd,HLCU,HLCU,Yes, -Hyundai Merchant Marine,Hyundai,HDMU,HDMU,Partial,No container numbers -Maersk,Maersk,MAEU,MAEU,Yes, -Matson Navigation Company,Matson,MATS,MATS,Yes, -Mediterranean Shipping Company,MSC,MSCU,MEDU,Yes, -Ocean Network Express,ONE,ONEY,ONEY,Yes, -Orient Overseas Container Line,OOCL,OOLU,OOLU,Yes, -Pacific International Lines,PIL,PCIU,PABV,Yes, -Safmarine Container Line,Safmarine,SAFM,SAFM,Yes, -SeaLand,SeaLand,SEAL,SEAU,Yes, -Yangming Marine Transport,Yangming,YMLU,YMLU,Yes, -Wan Hai Lines,Wan Hai,WHLC,WHLC,Yes, -Matson,Matson,MATS,,On roadmap, -Zim,Zim,ZIMU,ZIMU,Yes, -Seaboard Marine,Seaboard,CLAM,,On roadmap, -Crowley,Crowley,CLAM,,On roadmap, \ No newline at end of file diff --git a/Terminal49 Terminal Support.csv b/Terminal49 Terminal Support.csv deleted file mode 100644 index 54cc0e18..00000000 --- a/Terminal49 Terminal Support.csv +++ /dev/null @@ -1,47 +0,0 @@ -Terminal Name,Terminal Nickname,Firms Code,Port Name,Port Code,City,State,Country,Time Zone,Support -Dundalk Marine Terminal,Dundalk,A321,Baltimore,USBAL,Baltimore,MD,US,America/New_York,Yes -Seagirt Terminal,Seagirt,C324,Baltimore,USBAL,Baltimore,MD,US,America/New_York,Yes -Paul W Conley Terminal,Conley Terminal,A295,Boston,USBOS,Boston,MA,US,America/New_York,Yes -Barbours Cut Terminal,APM,S787,Houston,USHOU,Houston,TX,US,America/Chicago,Yes -Bayport Container Terminal,Bayport,V136,Houston,USHOU,Houston,TX,US,America/Chicago,Yes -SSA Cooper,SSA Cooper,N296,JAXPORT,USJAX,Jacksonville,FL,US,America/New_York,Yes -Trapac Jacksonville Terminal,Trapac Jacksonville,M029,JAXPORT,USJAX,Jacksonville,FL,US,America/New_York,Yes -Long Beach Container Terminal,LBCT Pier F,W183,Long Beach,USLGB,Los Angeles,CA,US,America/Los_Angeles,Yes -Long Beach Container Terminal,LBCT Pier E,WAC8,Long Beach,USLGB,Los Angeles,CA,US,America/Los_Angeles,Yes -Total Terminals International,TTI,Z952,Long Beach,USLGB,Los Angeles,CA,US,America/Los_Angeles,Yes -International Transportation Service,ITS,Y309,Long Beach,USLGB,Los Angeles,CA,US,America/Los_Angeles,Yes -Pacific Container Terminal,PCT,W182,Long Beach,USLGB,Los Angeles,CA,US,America/Los_Angeles,Yes -SSA Pier A,Pier A,Z978,Long Beach,USLGB,Los Angeles,CA,US,America/Los_Angeles,Yes -APM Terminals,APMT,W185,Los Angeles,USLAX,Los Angeles,CA,US,America/Los_Angeles,Yes -Fenix Marine Services,FMS,Y257,Los Angeles,USLAX,Los Angeles,CA,US,America/Los_Angeles,Yes -West Basin Container Terminal,WBCT,Y773,Los Angeles,USLAX,Los Angeles,CA,US,America/Los_Angeles,Yes -Everport Terminal Serivces,ETS,Y124,Los Angeles,USLAX,Los Angeles,CA,US,America/Los_Angeles,Yes -TraPac LA,TraPac LA,Y258,Los Angeles,USLAX,Los Angeles,CA,US,America/Los_Angeles,Yes -Yusen Terminals,Yusen,Y790,Los Angeles,USLAX,Los Angeles,CA,US,America/Los_Angeles,Yes -Port of Miami Terminal Operating Company,POMTOC,L239,Miami Seaport,USMIA,Miami,FL,US,America/New_York,Yes -South Florida Container Terminal,SFCT,N775,Miami Seaport,USMIA,Miami,FL,US,America/New_York,Yes -APM Terminal,APM Mobile,R103,Mobile,USMOB,Mobile,AL,US,America/Chicago,Yes -Napoleon Container Terminal,Napoleon,P260,New Orleans,USMSY,New Orleans,LA,US,America/Chicago,Yes -New Orleans Terminal,Milan,Q795,New Orleans,USMSY,New Orleans,LA,US,America/Chicago,Yes -Port Elizabeth,APM Terminals,E425,New York / New Jersey,USNYC,New York,NY,US,America/New_York,Yes -GCT New York LP,GCT,E005,New York / New Jersey,USNYC,New York,NY,US,America/New_York,Yes -Maher Terminals,Maher,E416,New York / New Jersey,USNYC,New York,NY,US,America/New_York,Yes -GCT Bayonne,GCTB,E364,New York / New Jersey,USNYC,New York,NY,US,America/New_York,Yes -Port Newark Container Terminal,PNCT,F577,New York / New Jersey,USNYC,New York,NY,US,America/New_York,Yes -Oakland International Container Terminal,SSA,Z985,Oakland,USOAK,Oakland,CA,US,America/Los_Angeles,Yes -TraPac Terminal,TraPac,Y549,Oakland,USOAK,Oakland,CA,US,America/Los_Angeles,Yes -Ben E. Nutter Terminal,Everport,Y738,Oakland,USOAK,Oakland,CA,US,America/Los_Angeles,Yes -Packer Avenue,Packer Avenue,C095,PhilaPort,USPHL,Philadelphia,PA,US,America/New_York,Yes -Florida International Terminal,FIT,N751,Port Everglades,USPEF,Fort Lauderdale,FL,US,America/New_York,Yes -Tampa Bay Container Terminal,Tampa Bay,M669,Port Tampa Bay,USPOT,Tampa Bay,FL,US,America/New_York,Yes -Garden City Terminals,GCT,L737,Savannah,USSAV,Savannah,GA,US,America/New_York,Yes -SSA Terminal 18,Terminal 18,X117,Seattle,USSEA,Seattle,WA,US,America/Los_Angeles,Yes -SSA Terminal 30,Terminal 30,Y197,Seattle,USSEA,Seattle,WA,US,America/Los_Angeles,Yes -Pierce County Terminal,PCT,X215,Tacoma,USTIW,Tacoma,WA,US,America/Los_Angeles,Yes -Husky Terminal,Husky,Z693,Tacoma,USTIW,Tacoma,WA,US,America/Los_Angeles,Yes -Washington United Terminals,WUT,Z705,Tacoma,USTIW,Tacoma,WA,US,America/Los_Angeles,Yes -Tacoma Container Terminal,TCT,WAM2,Tacoma,USTIW,Tacoma,WA,US,America/Los_Angeles,Yes -Richmond Marine Terminal,Richmond (RMT),LAQ4,Virginia,USORF,Norfolk,VA,US,America/New_York,Yes -Portsmouth Marine Terminal,Portsmouth (PMT),L045,Virginia,USORF,Norfolk,VA,US,America/New_York,Yes -Virginia International Gateway,Virginia (VIG),N195,Virginia,USORF,Norfolk,VA,US,America/New_York,Yes -Norfolk International Terminals,Norfolk (NIT),L005,Virginia,USORF,Norfolk,VA,US,America/New_York,Yes \ No newline at end of file diff --git a/Terminal49-API.postman_collection.json b/Terminal49-API.postman_collection.json deleted file mode 100644 index f3939905..00000000 --- a/Terminal49-API.postman_collection.json +++ /dev/null @@ -1,5965 +0,0 @@ -{ - "item": [ - { - "name": "Containers", - "description": "", - "item": [ - { - "id": "9bb8e3ef-61bd-4118-8d00-f842633e1634", - "name": "List containers", - "request": { - "name": "List containers", - "description": { - "content": "Returns a list of container. The containers are returned sorted by creation date, with the most recently refreshed containers appearing first.\n\nThis API will return all containers associated with the account.", - "type": "text/plain" - }, - "url": { - "path": [ - "containers" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[number]", - "value": "1" - }, - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[size]", - "value": "30" - }, - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include", - "type": "text/plain" - }, - "key": "include", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Number of seconds in which containers were refreshed", - "type": "text/plain" - }, - "key": "terminal_checked_before", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "6739eab6-3473-48c0-adac-2d9f7d2fa327", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "containers" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[number]", - "value": "1" - }, - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[size]", - "value": "30" - }, - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include", - "type": "text/plain" - }, - "key": "include", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Number of seconds in which containers were refreshed", - "type": "text/plain" - }, - "key": "terminal_checked_before", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"reefer\",\n \"equipment_length\": null,\n \"equipment_height\": \"high_cube\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"other\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"exam\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"dry\",\n \"equipment_length\": 45,\n \"equipment_height\": null,\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"total\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"total\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n }\n ],\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"all_containers_terminated\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"booking_cancelled\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "09bc2ec3-8158-4dfb-8e02-71c0dcb5105d", - "name": "Edit a container", - "request": { - "name": "Edit a container", - "description": { - "content": "Update a container", - "type": "text/plain" - }, - "url": { - "path": [ - "containers" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "PATCH", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"ref_numbers\": [\n \"\",\n \"\"\n ]\n },\n \"type\": false\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - }, - "auth": null - }, - "response": [ - { - "id": "bdfe6a47-ca82-45ae-b8ac-f9fbc1fd5fc3", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "containers" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PATCH", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"ref_numbers\": [\n \"\",\n \"\"\n ]\n },\n \"type\": false\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - } - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"dry\",\n \"equipment_length\": 40,\n \"equipment_height\": \"standard\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"demurrage\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"other\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "57eeff61-749c-479b-8437-3f441ff27522", - "name": "Get a container", - "request": { - "name": "Get a container", - "description": { - "content": "Retrieves the details of a container.", - "type": "text/plain" - }, - "url": { - "path": [ - "containers", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include", - "type": "text/plain" - }, - "key": "include", - "value": "" - } - ], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "0eec2b0c-2a5d-4b5d-9b9c-5ed52762261f", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "containers", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include", - "type": "text/plain" - }, - "key": "include", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"tank\",\n \"equipment_length\": 10,\n \"equipment_height\": \"standard\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"demurrage\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"exam\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"no_updates_at_line\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"cancelled_by_user\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "a09bb98f-1e38-4f5b-b026-0dc60f174fbf", - "name": "Get a container's raw events", - "request": { - "name": "Get a container's raw events", - "description": { - "content": "#### Deprecation warning\nThe `raw_events` endpoint is provided as-is.\n\n For past events we recommend consuming `transport_events`.\n\n---\nGet a list of past and future (estimated) milestones for a container as reported by the carrier. Some of the data is normalized even though the API is called raw_events. \n\nNormalized attributes: `event` and `timestamp` timestamp. Not all of the `event` values have been normalized. You can expect the the events related to container movements to be normalized but there are cases where events are not normalized. \n\nFor past historical events we recommend consuming `transport_events`. Although there are fewer events here those events go through additional vetting and normalization to avoid false positives and get you correct data.", - "type": "text/plain" - }, - "url": { - "path": [ - "containers", - ":id", - "raw_events" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "bee4e2dc-9210-4581-9615-58c9a6dd1e75", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "containers", - ":id", - "raw_events" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\",\n \"attributes\": {\n \"event\": \"transshipment_arrived\",\n \"original_event\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\",\n \"actual_on\": \"\",\n \"estimated_on\": \"\",\n \"actual_at\": \"\",\n \"estimated_at\": \"\",\n \"timezone\": \"\",\n \"created_at\": \"\",\n \"location_name\": \"\",\n \"location_locode\": \"\",\n \"vessel_name\": \"\",\n \"vessel_imo\": \"\",\n \"index\": \"\",\n \"voyage_number\": \"\"\n },\n \"relationships\": {\n \"location\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"metro_area\"\n }\n },\n \"vessel\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"vessel\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\",\n \"attributes\": {\n \"event\": \"delivered\",\n \"original_event\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\",\n \"actual_on\": \"\",\n \"estimated_on\": \"\",\n \"actual_at\": \"\",\n \"estimated_at\": \"\",\n \"timezone\": \"\",\n \"created_at\": \"\",\n \"location_name\": \"\",\n \"location_locode\": \"\",\n \"vessel_name\": \"\",\n \"vessel_imo\": \"\",\n \"index\": \"\",\n \"voyage_number\": \"\"\n },\n \"relationships\": {\n \"location\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"port\"\n }\n },\n \"vessel\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"vessel\"\n }\n }\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "762f9c6b-bcb4-4633-a6c7-9a315c3b1174", - "name": "Get a container's transport events", - "request": { - "name": "Get a container's transport events", - "description": { - "content": "Get a list of past transport events (canonical) for a container. All data has been normalized across all carriers. These are a verified subset of the raw events may also be sent as Webhook Notifications to a webhook endpoint.\n\nThis does not provide any estimated future events. See `container/:id/raw_events` endpoint for that. ", - "type": "text/plain" - }, - "url": { - "path": [ - "containers", - ":id", - "transport_events" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include", - "type": "text/plain" - }, - "key": "include", - "value": "" - } - ], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "b525349f-f02d-4881-a678-221a6c1f245f", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "containers", - ":id", - "transport_events" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include", - "type": "text/plain" - }, - "key": "include", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\",\n \"attributes\": {\n \"event\": \"container.transport.rail_unloaded\",\n \"voyage_number\": \"\",\n \"timestamp\": \"\",\n \"timezone\": \"\",\n \"location_locode\": \"\",\n \"created_at\": \"\",\n \"data_source\": \"shipping_line\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"location\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"metro_area\"\n }\n },\n \"vessel\": {\n \"data\": {\n \"id\": \"\",\n \"name\": \"vessel\"\n }\n },\n \"terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"container\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"container\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\",\n \"attributes\": {\n \"event\": \"container.transport.feeder_loaded\",\n \"voyage_number\": \"\",\n \"timestamp\": \"\",\n \"timezone\": \"\",\n \"location_locode\": \"\",\n \"created_at\": \"\",\n \"data_source\": \"terminal\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"location\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"metro_area\"\n }\n },\n \"vessel\": {\n \"data\": {\n \"id\": \"\",\n \"name\": \"vessel\"\n }\n },\n \"terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"rail_terminal\"\n }\n },\n \"container\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"container\"\n }\n }\n }\n }\n ],\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"past_arrival_window\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"cancelled_by_user\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"metro_area\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "36f2711e-ef03-4693-a396-0ae884ca5a4e", - "name": "Get container route", - "request": { - "name": "Get container route", - "description": { - "content": "Retrieves the route details from the port of lading to the port of discharge, including transshipments. This is a paid feature. Please contact sales@terminal49.com.", - "type": "text/plain" - }, - "url": { - "path": [ - "containers", - ":id", - "route" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "567040c7-e637-404b-851a-52650a10f42d", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "containers", - ":id", - "route" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"route\",\n \"attributes\": {\n \"id\": \"\",\n \"created_at\": \"\",\n \"updated_at\": \"\"\n },\n \"relationships\": {\n \"cargo\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"container\"\n }\n },\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"route_locations\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"route_location\"\n },\n {\n \"id\": \"\",\n \"type\": \"route_location\"\n }\n ]\n }\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"port\",\n \"attributes\": {\n \"name\": \"\",\n \"code\": \"\",\n \"state_abbr\": \"\",\n \"city\": \"\",\n \"country_code\": \"\",\n \"time_zone\": \"\",\n \"latitude\": \"\",\n \"longitude\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"port\",\n \"attributes\": {\n \"name\": \"\",\n \"code\": \"\",\n \"state_abbr\": \"\",\n \"city\": \"\",\n \"country_code\": \"\",\n \"time_zone\": \"\",\n \"latitude\": \"\",\n \"longitude\": \"\"\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "8c30a83e-93a7-41b0-ae31-4f7796ef42bb", - "name": "Forbidden - Routing data feature is not enabled for this account", - "originalRequest": { - "url": { - "path": [ - "containers", - ":id", - "route" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "Forbidden", - "code": 403, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n },\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "27b37327-b9ba-46d6-8bee-0d8abbad4659", - "name": "Refresh container", - "request": { - "name": "Refresh container", - "description": { - "content": "Schedules the container to be refreshed immediately from all relevant sources.

To be alerted of updates you should subscribe to the [relevant webhooks](/api-docs/in-depth-guides/webhooks). This endpoint is limited to 10 requests per minute.This is a paid feature. Please contact sales@terminal49.com.", - "type": "text/plain" - }, - "url": { - "path": [ - "containers", - ":id", - "refresh" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "PATCH", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "eb01f158-874b-496b-93f6-dd2d144c3203", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "containers", - ":id", - "refresh" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PATCH", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"message\": \"\"\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "c6b759c0-cf20-4997-bef3-2d0756e49d82", - "name": "Forbidden - This API endpoint is not enabled for your account. Please contact support@terminal49.com", - "originalRequest": { - "url": { - "path": [ - "containers", - ":id", - "refresh" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PATCH", - "body": {} - }, - "status": "Forbidden", - "code": 403, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n },\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "3b61917f-7049-473c-ab7c-54deb3621af4", - "name": "Too Many Requests - You've hit the refresh limit. Please try again in a minute.", - "originalRequest": { - "url": { - "path": [ - "containers", - ":id", - "refresh" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PATCH", - "body": {} - }, - "status": "Too Many Requests", - "code": 429, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "disabled": false, - "description": { - "content": "Number of seconds to wait before making another request", - "type": "text/plain" - }, - "key": "Retry-After", - "value": "" - } - ], - "body": "{\n \"errors\": [\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n },\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - } - ] - }, - { - "name": "Shipments", - "description": "", - "item": [ - { - "id": "8ba2991d-e0da-40e8-9e0e-b27225c39959", - "name": "List shipments", - "request": { - "name": "List shipments", - "description": { - "content": "Returns a list of your shipments. The shipments are returned sorted by creation date, with the most recent shipments appearing first.\n\nThis api will return all shipments associated with the account. Shipments created via the `tracking_request` API aswell as the ones added via the dashboard will be retuned via this endpoint. ", - "type": "text/plain" - }, - "url": { - "path": [ - "shipments" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "\n", - "type": "text/plain" - }, - "key": "page[number]", - "value": "1" - }, - { - "disabled": false, - "description": { - "content": "\n", - "type": "text/plain" - }, - "key": "page[size]", - "value": "30" - }, - { - "disabled": false, - "description": { - "content": "\nSearch shipments by master bill of lading, reference number, or container number.", - "type": "text/plain" - }, - "key": "q", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include", - "type": "text/plain" - }, - "key": "include", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Search shipments by the original request tracking `request_number`", - "type": "text/plain" - }, - "key": "number", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Filter shipments by whether they are still tracking or not", - "type": "text/plain" - }, - "key": "filter[tracking_stopped]", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "4f3ec712-ea7a-47a0-8abf-8b5036f30f50", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "shipments" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "\n", - "type": "text/plain" - }, - "key": "page[number]", - "value": "1" - }, - { - "disabled": false, - "description": { - "content": "\n", - "type": "text/plain" - }, - "key": "page[size]", - "value": "30" - }, - { - "disabled": false, - "description": { - "content": "\nSearch shipments by master bill of lading, reference number, or container number.", - "type": "text/plain" - }, - "key": "q", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include", - "type": "text/plain" - }, - "key": "include", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Search shipments by the original request tracking `request_number`", - "type": "text/plain" - }, - "key": "number", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Filter shipments by whether they are still tracking or not", - "type": "text/plain" - }, - "key": "filter[tracking_stopped]", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"all_containers_terminated\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"metro_area\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": null\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n ],\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"dry\",\n \"equipment_length\": 40,\n \"equipment_height\": null,\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"total\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"exam\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"tank\",\n \"equipment_length\": 10,\n \"equipment_height\": \"high_cube\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"demurrage\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"total\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "815b03aa-4940-480c-bd0f-0f15975be74f", - "name": "Unprocessable Entity", - "originalRequest": { - "url": { - "path": [ - "shipments" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "\n", - "type": "text/plain" - }, - "key": "page[number]", - "value": "1" - }, - { - "disabled": false, - "description": { - "content": "\n", - "type": "text/plain" - }, - "key": "page[size]", - "value": "30" - }, - { - "disabled": false, - "description": { - "content": "\nSearch shipments by master bill of lading, reference number, or container number.", - "type": "text/plain" - }, - "key": "q", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include", - "type": "text/plain" - }, - "key": "include", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Search shipments by the original request tracking `request_number`", - "type": "text/plain" - }, - "key": "number", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Filter shipments by whether they are still tracking or not", - "type": "text/plain" - }, - "key": "filter[tracking_stopped]", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "Unprocessable Entity (WebDAV) (RFC 4918)", - "code": 422, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"title\": \"\",\n \"detail\": \"\",\n \"source\": {\n \"pointer\": \"\",\n \"parameter\": \"\"\n },\n \"code\": \"\",\n \"status\": \"\",\n \"meta\": {\n \"tracking_request_id\": \"\"\n }\n },\n {\n \"title\": \"\",\n \"detail\": \"\",\n \"source\": {\n \"pointer\": \"\",\n \"parameter\": \"\"\n },\n \"code\": \"\",\n \"status\": \"\",\n \"meta\": {\n \"tracking_request_id\": \"\"\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "801cf6d7-17f9-429f-96d5-ba8242f089ce", - "name": "Get a shipment", - "request": { - "name": "Get a shipment", - "description": { - "content": "Retrieves the details of an existing shipment. You need only supply the unique shipment `id` that was returned upon `tracking_request` creation.", - "type": "text/plain" - }, - "url": { - "path": [ - "shipments", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include", - "type": "text/plain" - }, - "key": "include", - "value": "" - } - ], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) Shipment Id", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "d680d8e5-2002-459a-a37d-a8766d4cd0c8", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "shipments", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include", - "type": "text/plain" - }, - "key": "include", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"booking_cancelled\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"open top\",\n \"equipment_length\": 20,\n \"equipment_height\": \"high_cube\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"other\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"other\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"dry\",\n \"equipment_length\": 45,\n \"equipment_height\": \"high_cube\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"demurrage\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"other\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "ab1f1cea-f505-4177-9718-f95d18182147", - "name": "Not Found", - "originalRequest": { - "url": { - "path": [ - "shipments", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include", - "type": "text/plain" - }, - "key": "include", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "Not Found", - "code": 404, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"title\": \"\",\n \"detail\": \"\",\n \"source\": {\n \"pointer\": \"\",\n \"parameter\": \"\"\n },\n \"code\": \"\",\n \"status\": \"\",\n \"meta\": {\n \"tracking_request_id\": \"\"\n }\n },\n {\n \"title\": \"\",\n \"detail\": \"\",\n \"source\": {\n \"pointer\": \"\",\n \"parameter\": \"\"\n },\n \"code\": \"\",\n \"status\": \"\",\n \"meta\": {\n \"tracking_request_id\": \"\"\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "62f212db-b0c8-4c62-b003-570a5d11af48", - "name": "Edit a shipment", - "request": { - "name": "Edit a shipment", - "description": { - "content": "Update a shipment", - "type": "text/plain" - }, - "url": { - "path": [ - "shipments", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) Shipment Id", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "PATCH", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"shipment_tags\": [\n \"\",\n \"\"\n ]\n }\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - }, - "auth": null - }, - "response": [ - { - "id": "56d70a1f-6e3d-4286-9c68-80f2ad67ec04", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "shipments", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PATCH", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"shipment_tags\": [\n \"\",\n \"\"\n ]\n }\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - } - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"past_arrival_window\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "ee5c762c-97cf-436c-a2f4-abef84bd7bfb", - "name": "Stop tracking a shipment", - "request": { - "name": "Stop tracking a shipment", - "description": { - "content": "We'll stop tracking the shipment, which means that there will be no more updates. You can still access the shipment's previously-collected information via the API or dashboard.\n\nYou can resume tracking a shipment by calling the `resume_tracking` endpoint, but keep in mind that some information is only made available by our data sources at specific times, so a stopped and resumed shipment may have some information missing.", - "type": "text/plain" - }, - "url": { - "path": [ - "shipments", - ":id", - "stop_tracking" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "PATCH", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "7bc96ad1-bb86-4cb6-938d-20a98c4165e8", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "shipments", - ":id", - "stop_tracking" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PATCH", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"past_arrival_window\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "b3fa1be6-3755-4d5f-b288-5cdd899ef4bf", - "name": "Resume tracking a shipment", - "request": { - "name": "Resume tracking a shipment", - "description": { - "content": "Resume tracking a shipment. Keep in mind that some information is only made available by our data sources at specific times, so a stopped and resumed shipment may have some information missing.", - "type": "text/plain" - }, - "url": { - "path": [ - "shipments", - ":id", - "resume_tracking" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "PATCH", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "f2c30799-eb28-49a2-8a6e-b10ad61add9c", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "shipments", - ":id", - "resume_tracking" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PATCH", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"past_arrival_window\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - } - ] - }, - { - "name": "Locations", - "description": "", - "item": [] - }, - { - "name": "Events", - "description": "", - "item": [] - }, - { - "name": "Tracking Requests", - "description": "", - "item": [ - { - "id": "295ef0ce-9854-4b60-998b-955dcb5fba59", - "name": "Create a tracking request", - "request": { - "name": "Create a tracking request", - "description": { - "content": "To track an ocean shipment, you create a new tracking request. \nTwo attributes are required to track a shipment. A `bill of lading/booking number` and a shipping line `SCAC`. \n\nOnce a tracking request is created we will attempt to fetch the shipment details and it's related containers from the shipping line. If the attempt is successful we will create in new shipment object including any related container objects. We will send a `tracking_request.succeeded` webhook notification to your webhooks. \n\nIf the attempt to fetch fails then we will send a `tracking_request.failed` webhook notification to your `webhooks`. \n\nA `tracking_request.succeeded` or `tracking_request.failed` webhook notificaiton will only be sent if you have atleast one active webhook. ", - "type": "text/plain" - }, - "url": { - "path": [ - "tracking_requests" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "POST", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_type\": \"container\",\n \"request_number\": \"\",\n \"scac\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"shipment_tags\": [\n \"\",\n \"\"\n ]\n },\n \"relationships\": {\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - }, - "auth": { - "type": "apikey", - "apikey": [ - { - "key": "key", - "value": "Authorization" - }, - { - "key": "value", - "value": "{{apiKey}}" - }, - { - "key": "in", - "value": "header" - } - ] - } - }, - "response": [ - { - "id": "608ad242-fd2e-457f-9921-e3ee526d6ef1", - "name": "Tracking Request Created", - "originalRequest": { - "url": { - "path": [ - "tracking_requests" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "POST", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_type\": \"container\",\n \"request_number\": \"\",\n \"scac\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"shipment_tags\": [\n \"\",\n \"\"\n ]\n },\n \"relationships\": {\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - } - }, - "status": "Created", - "code": 201, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_number\": \"\",\n \"status\": \"created\",\n \"request_type\": \"booking_number\",\n \"scac\": \"\",\n \"created_at\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"tags\": [\n \"\",\n \"\"\n ],\n \"failed_reason\": \"internal_processing_error\",\n \"updated_at\": \"\",\n \"is_retrying\": \"\",\n \"retry_count\": \"\"\n },\n \"relationships\": {\n \"tracked_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "9bba0cb5-c83d-4fca-a8d4-d6ac1c5517cc", - "name": "Unprocessable Entity", - "originalRequest": { - "url": { - "path": [ - "tracking_requests" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "POST", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_type\": \"container\",\n \"request_number\": \"\",\n \"scac\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"shipment_tags\": [\n \"\",\n \"\"\n ]\n },\n \"relationships\": {\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - } - }, - "status": "Unprocessable Entity (WebDAV) (RFC 4918)", - "code": 422, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"title\": \"\",\n \"detail\": \"\",\n \"source\": {\n \"pointer\": \"\",\n \"parameter\": \"\"\n },\n \"code\": \"\",\n \"status\": \"\",\n \"meta\": {\n \"tracking_request_id\": \"\"\n }\n },\n {\n \"title\": \"\",\n \"detail\": \"\",\n \"source\": {\n \"pointer\": \"\",\n \"parameter\": \"\"\n },\n \"code\": \"\",\n \"status\": \"\",\n \"meta\": {\n \"tracking_request_id\": \"\"\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "be292a6c-595f-45d1-9955-019317c76c7e", - "name": "List tracking requests", - "request": { - "name": "List tracking requests", - "description": { - "content": "Returns a list of your tracking requests. The tracking requests are returned sorted by creation date, with the most recent tracking request appearing first.", - "type": "text/plain" - }, - "url": { - "path": [ - "tracking_requests" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "A search term to be applied against request_number and reference_numbers.", - "type": "text/plain" - }, - "key": "q", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "filter by `status`", - "type": "text/plain" - }, - "key": "filter[status]", - "value": "created" - }, - { - "disabled": false, - "description": { - "content": "filter by shipping line `scac`", - "type": "text/plain" - }, - "key": "filter[scac]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "filter by tracking_requests `created_at` after a certain ISO8601 timestamp", - "type": "text/plain" - }, - "key": "filter[created_at][start]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "filter by tracking_requests `created_at` before a certain ISO8601 timestamp", - "type": "text/plain" - }, - "key": "filter[created_at][end]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include. 'tracked_object' is included by default.", - "type": "text/plain" - }, - "key": "include", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[number]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[size]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "filter by `request_number`", - "type": "text/plain" - }, - "key": "filter[request_number]", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "293528e1-9911-474e-8beb-08643d69efce", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "tracking_requests" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "A search term to be applied against request_number and reference_numbers.", - "type": "text/plain" - }, - "key": "q", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "filter by `status`", - "type": "text/plain" - }, - "key": "filter[status]", - "value": "created" - }, - { - "disabled": false, - "description": { - "content": "filter by shipping line `scac`", - "type": "text/plain" - }, - "key": "filter[scac]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "filter by tracking_requests `created_at` after a certain ISO8601 timestamp", - "type": "text/plain" - }, - "key": "filter[created_at][start]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "filter by tracking_requests `created_at` before a certain ISO8601 timestamp", - "type": "text/plain" - }, - "key": "filter[created_at][end]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include. 'tracked_object' is included by default.", - "type": "text/plain" - }, - "key": "include", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[number]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[size]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "filter by `request_number`", - "type": "text/plain" - }, - "key": "filter[request_number]", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_number\": \"\",\n \"status\": \"awaiting_manifest\",\n \"request_type\": \"booking_number\",\n \"scac\": \"\",\n \"created_at\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"tags\": [\n \"\",\n \"\"\n ],\n \"failed_reason\": \"unrecognized_response\",\n \"updated_at\": \"\",\n \"is_retrying\": \"\",\n \"retry_count\": \"\"\n },\n \"relationships\": {\n \"tracked_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_number\": \"\",\n \"status\": \"pending\",\n \"request_type\": \"booking_number\",\n \"scac\": \"\",\n \"created_at\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"tags\": [\n \"\",\n \"\"\n ],\n \"failed_reason\": \"retries_exhausted\",\n \"updated_at\": \"\",\n \"is_retrying\": \"\",\n \"retry_count\": \"\"\n },\n \"relationships\": {\n \"tracked_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "5026aa08-352d-4156-bf2e-88a48173dbbf", - "name": "Not Found", - "originalRequest": { - "url": { - "path": [ - "tracking_requests" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "A search term to be applied against request_number and reference_numbers.", - "type": "text/plain" - }, - "key": "q", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "filter by `status`", - "type": "text/plain" - }, - "key": "filter[status]", - "value": "created" - }, - { - "disabled": false, - "description": { - "content": "filter by shipping line `scac`", - "type": "text/plain" - }, - "key": "filter[scac]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "filter by tracking_requests `created_at` after a certain ISO8601 timestamp", - "type": "text/plain" - }, - "key": "filter[created_at][start]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "filter by tracking_requests `created_at` before a certain ISO8601 timestamp", - "type": "text/plain" - }, - "key": "filter[created_at][end]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include. 'tracked_object' is included by default.", - "type": "text/plain" - }, - "key": "include", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[number]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[size]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "filter by `request_number`", - "type": "text/plain" - }, - "key": "filter[request_number]", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "Not Found", - "code": 404, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"title\": \"\",\n \"detail\": \"\",\n \"source\": {\n \"pointer\": \"\",\n \"parameter\": \"\"\n },\n \"code\": \"\",\n \"status\": \"\",\n \"meta\": {\n \"tracking_request_id\": \"\"\n }\n },\n {\n \"title\": \"\",\n \"detail\": \"\",\n \"source\": {\n \"pointer\": \"\",\n \"parameter\": \"\"\n },\n \"code\": \"\",\n \"status\": \"\",\n \"meta\": {\n \"tracking_request_id\": \"\"\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "01894ccd-eb5d-4ea6-ab9e-14cea1c6cc98", - "name": "Get a single tracking request", - "request": { - "name": "Get a single tracking request", - "description": { - "content": "Get the details and status of an existing tracking request. ", - "type": "text/plain" - }, - "url": { - "path": [ - "tracking_requests", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include. 'tracked_object' is included by default.", - "type": "text/plain" - }, - "key": "include", - "value": "" - } - ], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) Tracking Request ID", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "96a93a42-28e1-416c-afda-3fabb291ed58", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "tracking_requests", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include. 'tracked_object' is included by default.", - "type": "text/plain" - }, - "key": "include", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_number\": \"\",\n \"status\": \"created\",\n \"request_type\": \"booking_number\",\n \"scac\": \"\",\n \"created_at\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"tags\": [\n \"\",\n \"\"\n ],\n \"failed_reason\": \"internal_processing_error\",\n \"updated_at\": \"\",\n \"is_retrying\": \"\",\n \"retry_count\": \"\"\n },\n \"relationships\": {\n \"tracked_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "c4472456-580e-4be8-a4f9-6b2251bc3817", - "name": "Not Found", - "originalRequest": { - "url": { - "path": [ - "tracking_requests", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include. 'tracked_object' is included by default.", - "type": "text/plain" - }, - "key": "include", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "Not Found", - "code": 404, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"title\": \"\",\n \"detail\": \"\",\n \"source\": {\n \"pointer\": \"\",\n \"parameter\": \"\"\n },\n \"code\": \"\",\n \"status\": \"\",\n \"meta\": {\n \"tracking_request_id\": \"\"\n }\n },\n {\n \"title\": \"\",\n \"detail\": \"\",\n \"source\": {\n \"pointer\": \"\",\n \"parameter\": \"\"\n },\n \"code\": \"\",\n \"status\": \"\",\n \"meta\": {\n \"tracking_request_id\": \"\"\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "d40c8b0d-b0f1-455f-a735-4cfe896fd215", - "name": "Edit a tracking request", - "request": { - "name": "Edit a tracking request", - "description": { - "content": "Update a tracking request", - "type": "text/plain" - }, - "url": { - "path": [ - "tracking_requests", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) Tracking Request ID", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "PATCH", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"ref_number\": \"\"\n },\n \"type\": 5708.834400865539\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - }, - "auth": null - }, - "response": [ - { - "id": "b0973057-07ad-4533-930b-280b71f9f3c3", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "tracking_requests", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PATCH", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"ref_number\": \"\"\n },\n \"type\": 5708.834400865539\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - } - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_number\": \"\",\n \"status\": \"created\",\n \"request_type\": \"bill_of_lading\",\n \"scac\": \"\",\n \"created_at\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"tags\": [\n \"\",\n \"\"\n ],\n \"failed_reason\": \"shipping_line_unreachable\",\n \"updated_at\": \"\",\n \"is_retrying\": \"\",\n \"retry_count\": \"\"\n },\n \"relationships\": {\n \"tracked_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - } - ] - }, - { - "name": "Webhooks", - "description": "", - "item": [ - { - "id": "663154ff-3d01-495f-9a2e-bd18a059b479", - "name": "Get single webhook", - "request": { - "name": "Get single webhook", - "description": { - "content": "Get the details of a single webhook", - "type": "text/plain" - }, - "url": { - "path": [ - "webhooks", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "10169d40-8bdb-4c08-a0d1-c270f7a5d3d5", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "webhooks", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.vessel_loaded\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "35aa7806-b556-463d-9fa0-0386f4e3d4ef", - "name": "Edit a webhook", - "request": { - "name": "Edit a webhook", - "description": { - "content": "Update a single webhook", - "type": "text/plain" - }, - "url": { - "path": [ - "webhooks", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "PATCH", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"url\": \"\",\n \"events\": [\n \"container.transport.vessel_loaded\"\n ],\n \"active\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n },\n \"type\": \"webhook\"\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - }, - "auth": null - }, - "response": [ - { - "id": "0eea93c4-a52a-44d9-b29f-e806ff33277d", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "webhooks", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PATCH", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"url\": \"\",\n \"events\": [\n \"container.transport.vessel_loaded\"\n ],\n \"active\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n },\n \"type\": \"webhook\"\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - } - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.vessel_loaded\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "656b9cf1-4a82-45c8-8a06-802f60bef0d9", - "name": "Delete a webhook", - "request": { - "name": "Delete a webhook", - "description": { - "content": "Delete a webhook", - "type": "text/plain" - }, - "url": { - "path": [ - "webhooks", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "method": "DELETE", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "e16c42ab-da0b-4aff-a79f-6b4124b0c09a", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "webhooks", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "DELETE", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [], - "cookie": [], - "_postman_previewlanguage": "text" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "9fc3402c-127e-4cdf-8b2b-b0a506212b47", - "name": "List webhooks", - "request": { - "name": "List webhooks", - "description": { - "content": "Get a list of all the webhooks", - "type": "text/plain" - }, - "url": { - "path": [ - "webhooks" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[number]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[size]", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "e03e44a4-6a2e-4948-8606-44360d67a0b6", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "webhooks" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[number]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[size]", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.transshipment_arrived\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.empty_out\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n ],\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n },\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "6f24513e-b84e-4ec0-a9aa-ded1775a3cf0", - "name": "Create a webhook", - "request": { - "name": "Create a webhook", - "description": { - "content": "You can configure a webhook via the API to be notified about events that happen in your Terminal49 account. These events can be realted to tracking_requests, shipments and containers. \n\nThis is the recommended way tracking shipments and containers via the API. You should use this instead of polling our the API periodically. ", - "type": "text/plain" - }, - "url": { - "path": [ - "webhooks" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "POST", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"url\": \"\",\n \"active\": \"\",\n \"events\": [\n \"container.transport.rail_unloaded\"\n ],\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n },\n \"type\": \"webhook\"\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - }, - "auth": null - }, - "response": [ - { - "id": "f0fbf31c-8c03-4fba-90ed-1079d7164859", - "name": "Create a test webhook endpoint", - "originalRequest": { - "url": { - "path": [ - "webhooks" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "POST", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"url\": \"\",\n \"active\": \"\",\n \"events\": [\n \"container.transport.rail_unloaded\"\n ],\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n },\n \"type\": \"webhook\"\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - } - }, - "status": "Created", - "code": 201, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.vessel_loaded\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "e82d0249-3d66-40a4-98c3-cf40348b406b", - "name": "List webhook IPs", - "request": { - "name": "List webhook IPs", - "description": { - "content": "Return the list of IPs used for sending webhook notifications. This can be useful for whitelisting the IPs on the firewall.", - "type": "text/plain" - }, - "url": { - "path": [ - "webhooks", - "ips" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "7c861e2a-450c-4e51-a54e-7148222bc67d", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "webhooks", - "ips" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"webhook_notification_ips\": [\n \"\",\n \"\"\n ],\n \"last_updated\": \"\"\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - } - ] - }, - { - "name": "Webhook Notifications", - "description": "", - "item": [ - { - "id": "388d7c75-0915-4456-a1c2-40749438f043", - "name": "Get a single webhook notification", - "request": { - "name": "Get a single webhook notification", - "description": { - "content": "\n", - "type": "text/plain" - }, - "url": { - "path": [ - "webhook_notifications", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include.", - "type": "text/plain" - }, - "key": "include", - "value": "" - } - ], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "67c05253-c558-49a9-afc9-9d799e1543b9", - "originalRequest": { - "url": { - "path": [ - "webhook_notifications", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include.", - "type": "text/plain" - }, - "key": "include", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook_notification\",\n \"attributes\": {\n \"event\": \"container.transport.estimated.arrived_at_inland_destination\",\n \"delivery_status\": \"pending\",\n \"created_at\": \"\"\n },\n \"relationships\": {\n \"webhook\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\"\n }\n },\n \"reference_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"estimated_event\"\n }\n }\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"tracking_request.succeeded\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.vessel_berthed\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "e5f530bb-1ad8-4b0c-84af-ace6b8cc9b7a", - "name": "List webhook notifications", - "request": { - "name": "List webhook notifications", - "description": { - "content": "Return the list of webhook notifications. This can be useful for reconciling your data if your endpoint has been down. ", - "type": "text/plain" - }, - "url": { - "path": [ - "webhook_notifications" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[number]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[size]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include.", - "type": "text/plain" - }, - "key": "include", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "d6672710-17f6-458c-b819-99fae0b72da1", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "webhook_notifications" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[number]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[size]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "Comma delimited list of relations to include.", - "type": "text/plain" - }, - "key": "include", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"webhook_notification\",\n \"attributes\": {\n \"event\": \"container.pod_terminal_changed\",\n \"delivery_status\": \"pending\",\n \"created_at\": \"\"\n },\n \"relationships\": {\n \"webhook\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\"\n }\n },\n \"reference_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"tracking_request\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook_notification\",\n \"attributes\": {\n \"event\": \"tracking_request.succeeded\",\n \"delivery_status\": \"pending\",\n \"created_at\": \"\"\n },\n \"relationships\": {\n \"webhook\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\"\n }\n },\n \"reference_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"tracking_request\"\n }\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.rail_arrived\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.feeder_departed\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "344622c7-bfbb-4fca-b117-124aba15192e", - "name": "Get webhook notification payload examples", - "request": { - "name": "Get webhook notification payload examples", - "description": { - "content": "Returns an example payload as it would be sent to a webhook endpoint for the provided `event` ", - "type": "text/plain" - }, - "url": { - "path": [ - "webhook_notifications", - "examples" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "The webhook notification event name you wish to see an example of", - "type": "text/plain" - }, - "key": "event", - "value": "container.transport.transshipment_departed" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "53afc563-5637-46cb-9d55-e0b1fd01f3b1", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "webhook_notifications", - "examples" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "The webhook notification event name you wish to see an example of", - "type": "text/plain" - }, - "key": "event", - "value": "container.transport.transshipment_departed" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"webhook_notification\",\n \"attributes\": {\n \"event\": \"container.pod_terminal_changed\",\n \"delivery_status\": \"pending\",\n \"created_at\": \"\"\n },\n \"relationships\": {\n \"webhook\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\"\n }\n },\n \"reference_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"tracking_request\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook_notification\",\n \"attributes\": {\n \"event\": \"tracking_request.succeeded\",\n \"delivery_status\": \"pending\",\n \"created_at\": \"\"\n },\n \"relationships\": {\n \"webhook\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\"\n }\n },\n \"reference_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"tracking_request\"\n }\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.rail_arrived\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.feeder_departed\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - } - ] - }, - { - "name": "Ports", - "description": "", - "item": [ - { - "id": "929c41c1-dd05-47d5-aaa8-06a41bd750b6", - "name": "Get a port using the locode or the id", - "request": { - "name": "Get a port using the locode or the id", - "description": { - "content": "Return the details of a single port.", - "type": "text/plain" - }, - "url": { - "path": [ - "ports", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "22885b2e-21d0-4888-ac33-415187f4cc5a", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "ports", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"port\",\n \"attributes\": {\n \"name\": \"\",\n \"code\": \"\",\n \"state_abbr\": \"\",\n \"city\": \"\",\n \"country_code\": \"\",\n \"time_zone\": \"\",\n \"latitude\": \"\",\n \"longitude\": \"\"\n }\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - } - ] - }, - { - "name": "Metro Areas", - "description": "", - "item": [ - { - "id": "082e6784-c82d-4d7c-bde0-74b9b0e3e516", - "name": "Get a metro area using the un/locode or the id", - "request": { - "name": "Get a metro area using the un/locode or the id", - "description": { - "content": "Return the details of a single metro area.", - "type": "text/plain" - }, - "url": { - "path": [ - "metro_areas", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "45719ac3-69b1-401f-8938-da617a7a1e19", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "metro_areas", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"metro_area\",\n \"attributes\": {\n \"name\": \"\",\n \"code\": \"\",\n \"state_abbr\": \"\",\n \"country_code\": \"\",\n \"time_zone\": \"\",\n \"latitude\": \"\",\n \"longitude\": \"\"\n },\n \"\": \"\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - } - ] - }, - { - "name": "Terminals", - "description": "", - "item": [ - { - "id": "79a5e561-ebb3-4990-80fe-ef55cf1ecba5", - "name": "Get a terminal using the id", - "request": { - "name": "Get a terminal using the id", - "description": { - "content": "Return the details of a single terminal.", - "type": "text/plain" - }, - "url": { - "path": [ - "terminals", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "b75ef20a-2bf9-4da0-ba49-54257847daf3", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "terminals", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"attributes\": {\n \"name\": \"\",\n \"nickname\": \"\",\n \"firms_code\": \"\",\n \"smdg_code\": \"\",\n \"bic_facility_code\": \"\",\n \"street\": \"\",\n \"city\": \"\",\n \"state\": \"\",\n \"state_abbr\": \"\",\n \"zip\": \"\",\n \"country\": \"\"\n },\n \"relationships\": {\n \"port\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"port\"\n }\n }\n },\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - } - ] - }, - { - "name": "Routing (Paid)", - "description": "", - "item": [ - { - "id": "475bced9-913f-4994-b748-3c392087d7db", - "name": "Get container route", - "request": { - "name": "Get container route", - "description": { - "content": "Retrieves the route details from the port of lading to the port of discharge, including transshipments. This is a paid feature. Please contact sales@terminal49.com.", - "type": "text/plain" - }, - "url": { - "path": [ - "containers", - ":id", - "route" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "ff0e08b8-a049-4fa5-9b99-15ec84bca84c", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "containers", - ":id", - "route" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"route\",\n \"attributes\": {\n \"id\": \"\",\n \"created_at\": \"\",\n \"updated_at\": \"\"\n },\n \"relationships\": {\n \"cargo\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"container\"\n }\n },\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"route_locations\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"route_location\"\n },\n {\n \"id\": \"\",\n \"type\": \"route_location\"\n }\n ]\n }\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"port\",\n \"attributes\": {\n \"name\": \"\",\n \"code\": \"\",\n \"state_abbr\": \"\",\n \"city\": \"\",\n \"country_code\": \"\",\n \"time_zone\": \"\",\n \"latitude\": \"\",\n \"longitude\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"port\",\n \"attributes\": {\n \"name\": \"\",\n \"code\": \"\",\n \"state_abbr\": \"\",\n \"city\": \"\",\n \"country_code\": \"\",\n \"time_zone\": \"\",\n \"latitude\": \"\",\n \"longitude\": \"\"\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "4414a19d-c226-4268-bb31-0da543b17b6f", - "name": "Forbidden - Routing data feature is not enabled for this account", - "originalRequest": { - "url": { - "path": [ - "containers", - ":id", - "route" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "Forbidden", - "code": 403, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n },\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "b2ab7690-886f-4c36-b205-ef087dc47582", - "name": "Get vessel future positions", - "request": { - "name": "Get vessel future positions", - "description": { - "content": "Returns the estimated route between two ports for a given vessel. The timestamp of the positions has fixed spacing of one minute. This is a paid feature. Please contact sales@terminal49.com.", - "type": "text/plain" - }, - "url": { - "path": [ - "vessels", - ":id", - "future_positions" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "(Required) The destination port id", - "type": "text/plain" - }, - "key": "port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) The previous port id", - "type": "text/plain" - }, - "key": "previous_port_id", - "value": "" - } - ], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "df02df9b-171e-4273-b0fa-3f3dc53a3e21", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "vessels", - ":id", - "future_positions" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "(Required) The destination port id", - "type": "text/plain" - }, - "key": "port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) The previous port id", - "type": "text/plain" - }, - "key": "previous_port_id", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"vessel\",\n \"attributes\": {\n \"name\": \"\",\n \"imo\": \"\",\n \"mmsi\": \"\",\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"nautical_speed_knots\": \"\",\n \"navigational_heading_degrees\": \"\",\n \"position_timestamp\": \"\",\n \"positions\": [\n {\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\",\n \"heading\": \"\"\n },\n {\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\",\n \"heading\": \"\"\n }\n ]\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "23663fc6-40cb-430d-b394-6dc5e5106606", - "name": "Forbidden - Routing data feature is not enabled for this account", - "originalRequest": { - "url": { - "path": [ - "vessels", - ":id", - "future_positions" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "(Required) The destination port id", - "type": "text/plain" - }, - "key": "port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) The previous port id", - "type": "text/plain" - }, - "key": "previous_port_id", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "Forbidden", - "code": 403, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n },\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "3da347c8-4bb9-42f3-82da-261f1880108c", - "name": "Get vessel future positions from coordinates", - "request": { - "name": "Get vessel future positions from coordinates", - "description": { - "content": "Returns the estimated route between two ports for a given vessel from a set of coordinates. The timestamp of the positions has fixed spacing of one minute. This is a paid feature. Please contact sales@terminal49.com.", - "type": "text/plain" - }, - "url": { - "path": [ - "vessels", - ":id", - "future_positions_with_coordinates" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "(Required) The destination port id", - "type": "text/plain" - }, - "key": "port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) The previous port id", - "type": "text/plain" - }, - "key": "previous_port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) Starting latitude coordinate", - "type": "text/plain" - }, - "key": "latitude", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) Starting longitude coordinate", - "type": "text/plain" - }, - "key": "longitude", - "value": "" - } - ], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "755e5c99-d756-4a27-910c-3ad42c910649", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "vessels", - ":id", - "future_positions_with_coordinates" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "(Required) The destination port id", - "type": "text/plain" - }, - "key": "port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) The previous port id", - "type": "text/plain" - }, - "key": "previous_port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) Starting latitude coordinate", - "type": "text/plain" - }, - "key": "latitude", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) Starting longitude coordinate", - "type": "text/plain" - }, - "key": "longitude", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"vessel\",\n \"attributes\": {\n \"name\": \"\",\n \"imo\": \"\",\n \"mmsi\": \"\",\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"nautical_speed_knots\": \"\",\n \"navigational_heading_degrees\": \"\",\n \"position_timestamp\": \"\",\n \"positions\": [\n {\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\",\n \"heading\": \"\"\n },\n {\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\",\n \"heading\": \"\"\n }\n ]\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "47638ced-0598-4f7a-a5a4-3c193b22f6da", - "name": "Forbidden - Routing data feature is not enabled for this account", - "originalRequest": { - "url": { - "path": [ - "vessels", - ":id", - "future_positions_with_coordinates" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "(Required) The destination port id", - "type": "text/plain" - }, - "key": "port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) The previous port id", - "type": "text/plain" - }, - "key": "previous_port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) Starting latitude coordinate", - "type": "text/plain" - }, - "key": "latitude", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) Starting longitude coordinate", - "type": "text/plain" - }, - "key": "longitude", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "Forbidden", - "code": 403, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n },\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - } - ] - }, - { - "name": "Shipping Lines", - "description": "", - "item": [ - { - "id": "e9ce2ba1-35aa-4fe5-a6d6-9c797da66c47", - "name": "Shipping Lines", - "request": { - "name": "Shipping Lines", - "description": { - "content": "Return a list of shipping lines supported by Terminal49. \nN.B. There is no pagination for this endpoint.", - "type": "text/plain" - }, - "url": { - "path": [ - "shipping_lines" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "fd865f9a-4fc0-4f63-bc2a-5d4b15e421fa", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "shipping_lines" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"attributes\": {\n \"scac\": \"\",\n \"name\": \"\",\n \"alternative_scacs\": [\n \"\",\n \"\"\n ],\n \"short_name\": \"\",\n \"bill_of_lading_tracking_support\": \"\",\n \"booking_number_tracking_support\": \"\",\n \"container_number_tracking_support\": \"\"\n },\n \"type\": \"shipping_line\"\n },\n {\n \"id\": \"\",\n \"attributes\": {\n \"scac\": \"\",\n \"name\": \"\",\n \"alternative_scacs\": [\n \"\",\n \"\"\n ],\n \"short_name\": \"\",\n \"bill_of_lading_tracking_support\": \"\",\n \"booking_number_tracking_support\": \"\",\n \"container_number_tracking_support\": \"\"\n },\n \"type\": \"shipping_line\"\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "860061bb-9a94-43c0-881b-40ec0d41b7fa", - "name": "Get a single shipping line", - "request": { - "name": "Get a single shipping line", - "description": { - "content": "Return the details of a single shipping line.", - "type": "text/plain" - }, - "url": { - "path": [ - "shipping_lines", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "e2c0fe75-1fd4-4012-81c2-e6e858746ef9", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "shipping_lines", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"attributes\": {\n \"scac\": \"\",\n \"name\": \"\",\n \"alternative_scacs\": [\n \"\",\n \"\"\n ],\n \"short_name\": \"\",\n \"bill_of_lading_tracking_support\": \"\",\n \"booking_number_tracking_support\": \"\",\n \"container_number_tracking_support\": \"\"\n },\n \"type\": \"shipping_line\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - } - ] - }, - { - "name": "Vessels", - "description": "", - "item": [ - { - "id": "9c6e4bfa-dbed-46b1-aff0-e7e9df62cf9a", - "name": "Get a vessel using the id", - "request": { - "name": "Get a vessel using the id", - "description": { - "content": "Returns a vessel by id. `show_positions` is a paid feature. Please contact sales@terminal49.com.", - "type": "text/plain" - }, - "url": { - "path": [ - "vessels", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "ISO 8601 timestamp to filter positions from. 7 days by default.", - "type": "text/plain" - }, - "key": "show_positions[from_timestamp]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "ISO 8601 timestamp to filter positions up to. Current time by default.", - "type": "text/plain" - }, - "key": "show_positions[to_timestamp]", - "value": "" - } - ], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "7058316c-11a8-49ed-b7ec-9ec8c8e3a4d6", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "vessels", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "ISO 8601 timestamp to filter positions from. 7 days by default.", - "type": "text/plain" - }, - "key": "show_positions[from_timestamp]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "ISO 8601 timestamp to filter positions up to. Current time by default.", - "type": "text/plain" - }, - "key": "show_positions[to_timestamp]", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"vessel\",\n \"attributes\": {\n \"name\": \"\",\n \"imo\": \"\",\n \"mmsi\": \"\",\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"nautical_speed_knots\": \"\",\n \"navigational_heading_degrees\": \"\",\n \"position_timestamp\": \"\",\n \"positions\": [\n {\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"heading\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\"\n },\n {\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"heading\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\"\n }\n ]\n }\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "b293332c-60e9-4ce9-b9da-40d13b585ee7", - "name": "Forbidden - Feature not enabled", - "originalRequest": { - "url": { - "path": [ - "vessels", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "ISO 8601 timestamp to filter positions from. 7 days by default.", - "type": "text/plain" - }, - "key": "show_positions[from_timestamp]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "ISO 8601 timestamp to filter positions up to. Current time by default.", - "type": "text/plain" - }, - "key": "show_positions[to_timestamp]", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "Forbidden", - "code": 403, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"status\": \"\",\n \"source\": {},\n \"title\": \"\",\n \"detail\": \"\"\n },\n {\n \"status\": \"\",\n \"source\": {},\n \"title\": \"\",\n \"detail\": \"\"\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "6809f936-176a-4ad3-a303-ed98178baf2c", - "name": "Get a vessel using the imo", - "request": { - "name": "Get a vessel using the imo", - "description": { - "content": "Returns a vessel by the given IMO number. `show_positions` is a paid feature. Please contact sales@terminal49.com.", - "type": "text/plain" - }, - "url": { - "path": [ - "vessels", - ":imo" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "ISO 8601 timestamp to filter positions from. 7 days by default.", - "type": "text/plain" - }, - "key": "show_positions[from_timestamp]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "ISO 8601 timestamp to filter positions up to. Current time by default.", - "type": "text/plain" - }, - "key": "show_positions[to_timestamp]", - "value": "" - } - ], - "variable": [ - { - "type": "any", - "value": "", - "key": "imo", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "7c79a4f7-2ef2-436f-8946-7fba1dbb962a", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "vessels", - ":imo" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "ISO 8601 timestamp to filter positions from. 7 days by default.", - "type": "text/plain" - }, - "key": "show_positions[from_timestamp]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "ISO 8601 timestamp to filter positions up to. Current time by default.", - "type": "text/plain" - }, - "key": "show_positions[to_timestamp]", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"vessel\",\n \"attributes\": {\n \"name\": \"\",\n \"imo\": \"\",\n \"mmsi\": \"\",\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"nautical_speed_knots\": \"\",\n \"navigational_heading_degrees\": \"\",\n \"position_timestamp\": \"\",\n \"positions\": [\n {\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"heading\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\"\n },\n {\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"heading\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\"\n }\n ]\n }\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "46bf6b1d-2923-454f-bf75-b1f8e1f7bba2", - "name": "Forbidden - Feature not enabled", - "originalRequest": { - "url": { - "path": [ - "vessels", - ":imo" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "ISO 8601 timestamp to filter positions from. 7 days by default.", - "type": "text/plain" - }, - "key": "show_positions[from_timestamp]", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "ISO 8601 timestamp to filter positions up to. Current time by default.", - "type": "text/plain" - }, - "key": "show_positions[to_timestamp]", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "Forbidden", - "code": 403, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"status\": \"\",\n \"source\": {},\n \"title\": \"\",\n \"detail\": \"\"\n },\n {\n \"status\": \"\",\n \"source\": {},\n \"title\": \"\",\n \"detail\": \"\"\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "c8da6a8c-aaa3-4de2-a478-6ccff92b0231", - "name": "Get vessel future positions", - "request": { - "name": "Get vessel future positions", - "description": { - "content": "Returns the estimated route between two ports for a given vessel. The timestamp of the positions has fixed spacing of one minute. This is a paid feature. Please contact sales@terminal49.com.", - "type": "text/plain" - }, - "url": { - "path": [ - "vessels", - ":id", - "future_positions" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "(Required) The destination port id", - "type": "text/plain" - }, - "key": "port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) The previous port id", - "type": "text/plain" - }, - "key": "previous_port_id", - "value": "" - } - ], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "f564746c-459b-41a3-b741-f746a122f4e8", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "vessels", - ":id", - "future_positions" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "(Required) The destination port id", - "type": "text/plain" - }, - "key": "port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) The previous port id", - "type": "text/plain" - }, - "key": "previous_port_id", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"vessel\",\n \"attributes\": {\n \"name\": \"\",\n \"imo\": \"\",\n \"mmsi\": \"\",\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"nautical_speed_knots\": \"\",\n \"navigational_heading_degrees\": \"\",\n \"position_timestamp\": \"\",\n \"positions\": [\n {\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\",\n \"heading\": \"\"\n },\n {\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\",\n \"heading\": \"\"\n }\n ]\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "d617d3e5-b77a-4998-8b0b-4e986f453069", - "name": "Forbidden - Routing data feature is not enabled for this account", - "originalRequest": { - "url": { - "path": [ - "vessels", - ":id", - "future_positions" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "(Required) The destination port id", - "type": "text/plain" - }, - "key": "port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) The previous port id", - "type": "text/plain" - }, - "key": "previous_port_id", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "Forbidden", - "code": 403, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n },\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "c18918a4-3c4b-428d-b3ae-36a401e86603", - "name": "Get vessel future positions from coordinates", - "request": { - "name": "Get vessel future positions from coordinates", - "description": { - "content": "Returns the estimated route between two ports for a given vessel from a set of coordinates. The timestamp of the positions has fixed spacing of one minute. This is a paid feature. Please contact sales@terminal49.com.", - "type": "text/plain" - }, - "url": { - "path": [ - "vessels", - ":id", - "future_positions_with_coordinates" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "(Required) The destination port id", - "type": "text/plain" - }, - "key": "port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) The previous port id", - "type": "text/plain" - }, - "key": "previous_port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) Starting latitude coordinate", - "type": "text/plain" - }, - "key": "latitude", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) Starting longitude coordinate", - "type": "text/plain" - }, - "key": "longitude", - "value": "" - } - ], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "36c31554-4c64-4441-93ca-7ed682c0cc89", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "vessels", - ":id", - "future_positions_with_coordinates" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "(Required) The destination port id", - "type": "text/plain" - }, - "key": "port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) The previous port id", - "type": "text/plain" - }, - "key": "previous_port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) Starting latitude coordinate", - "type": "text/plain" - }, - "key": "latitude", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) Starting longitude coordinate", - "type": "text/plain" - }, - "key": "longitude", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"vessel\",\n \"attributes\": {\n \"name\": \"\",\n \"imo\": \"\",\n \"mmsi\": \"\",\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"nautical_speed_knots\": \"\",\n \"navigational_heading_degrees\": \"\",\n \"position_timestamp\": \"\",\n \"positions\": [\n {\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\",\n \"heading\": \"\"\n },\n {\n \"latitude\": \"\",\n \"longitude\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\",\n \"heading\": \"\"\n }\n ]\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "e769962e-1fc6-4e13-bce6-ea7dbc3bf66b", - "name": "Forbidden - Routing data feature is not enabled for this account", - "originalRequest": { - "url": { - "path": [ - "vessels", - ":id", - "future_positions_with_coordinates" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "(Required) The destination port id", - "type": "text/plain" - }, - "key": "port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) The previous port id", - "type": "text/plain" - }, - "key": "previous_port_id", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) Starting latitude coordinate", - "type": "text/plain" - }, - "key": "latitude", - "value": "" - }, - { - "disabled": false, - "description": { - "content": "(Required) Starting longitude coordinate", - "type": "text/plain" - }, - "key": "longitude", - "value": "" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "Forbidden", - "code": 403, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n },\n {\n \"status\": \"\",\n \"title\": \"\",\n \"detail\": \"\"\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - } - ] - }, - { - "name": "Parties", - "description": "", - "item": [ - { - "id": "e69551d0-da77-485a-a3b6-4b982af934e1", - "name": "list-parties", - "request": { - "name": "list-parties", - "description": { - "content": "Get a list of parties", - "type": "text/plain" - }, - "url": { - "path": [ - "parties" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[number]", - "value": "1" - }, - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[size]", - "value": "25" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "3fd1ade3-98ab-46bf-8bb2-4af3f461b2be", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "parties" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [ - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[number]", - "value": "1" - }, - { - "disabled": false, - "description": { - "content": "", - "type": "text/plain" - }, - "key": "page[size]", - "value": "25" - } - ], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": [\n {\n \"attributes\": {\n \"company_name\": \"\"\n },\n \"id\": \"\",\n \"type\": \"party\"\n },\n {\n \"attributes\": {\n \"company_name\": \"\"\n },\n \"id\": \"\",\n \"type\": \"party\"\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "d59a0975-3865-434e-a32b-1e823f11faf9", - "name": "post-party", - "request": { - "name": "post-party", - "description": { - "content": "Creates a new party", - "type": "text/plain" - }, - "url": { - "path": [ - "parties" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "POST", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"company_name\": \"\"\n }\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - }, - "auth": null - }, - "response": [ - { - "id": "0a626240-5bef-41b9-be89-ab1ad4000b89", - "name": "Party Created", - "originalRequest": { - "url": { - "path": [ - "parties" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "POST", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"company_name\": \"\"\n }\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - } - }, - "status": "Created", - "code": 201, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"attributes\": {\n \"company_name\": \"\"\n },\n \"id\": \"\",\n \"type\": \"party\"\n },\n \"links\": {\n \"self\": \"\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "d973b13c-cd56-48ce-bf29-f8d1d5c6ae43", - "name": "Unprocessable Entity", - "originalRequest": { - "url": { - "path": [ - "parties" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "POST", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"company_name\": \"\"\n }\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - } - }, - "status": "Unprocessable Entity (WebDAV) (RFC 4918)", - "code": 422, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"title\": \"\",\n \"detail\": \"\",\n \"source\": {\n \"pointer\": \"\",\n \"parameter\": \"\"\n },\n \"code\": \"\",\n \"status\": \"\",\n \"meta\": {\n \"tracking_request_id\": \"\"\n }\n },\n {\n \"title\": \"\",\n \"detail\": \"\",\n \"source\": {\n \"pointer\": \"\",\n \"parameter\": \"\"\n },\n \"code\": \"\",\n \"status\": \"\",\n \"meta\": {\n \"tracking_request_id\": \"\"\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "e11c88ad-3c62-48cb-b2a2-f0d404e90fab", - "name": "get-parties-id", - "request": { - "name": "get-parties-id", - "description": { - "content": "Returns a party by it's given identifier", - "type": "text/plain" - }, - "url": { - "path": [ - "parties", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "body": {}, - "auth": null - }, - "response": [ - { - "id": "54a03d51-09dd-4bc3-973a-f7ad72759e3d", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "parties", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "body": {} - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"attributes\": {\n \"company_name\": \"\"\n },\n \"id\": \"\",\n \"type\": \"party\"\n },\n \"links\": {\n \"self\": \"\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - }, - { - "id": "970c1319-86b7-4b7a-8a10-93e1c175cb8f", - "name": "edit-party", - "request": { - "name": "edit-party", - "description": { - "content": "Updates a party", - "type": "text/plain" - }, - "url": { - "path": [ - "parties", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [ - { - "type": "any", - "value": "", - "key": "id", - "disabled": false, - "description": { - "content": "(Required) ", - "type": "text/plain" - } - } - ] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "PATCH", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"company_name\": \"\"\n }\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - }, - "auth": null - }, - "response": [ - { - "id": "10d23728-c81b-4f7f-a4d4-93031383c207", - "name": "OK", - "originalRequest": { - "url": { - "path": [ - "parties", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PATCH", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"company_name\": \"\"\n }\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - } - }, - "status": "OK", - "code": 200, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"data\": {\n \"attributes\": {\n \"company_name\": \"\"\n },\n \"id\": \"\",\n \"type\": \"party\"\n },\n \"links\": {\n \"self\": \"\"\n }\n}", - "cookie": [], - "_postman_previewlanguage": "json" - }, - { - "id": "2bf770f0-f661-45f0-a6e6-3775074d78d1", - "name": "Unprocessable Entity", - "originalRequest": { - "url": { - "path": [ - "parties", - ":id" - ], - "host": [ - "{{baseUrl}}" - ], - "query": [], - "variable": [] - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PATCH", - "body": { - "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"company_name\": \"\"\n }\n }\n}", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - } - } - }, - "status": "Unprocessable Entity (WebDAV) (RFC 4918)", - "code": 422, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": "{\n \"errors\": [\n {\n \"title\": \"\",\n \"detail\": \"\",\n \"source\": {\n \"pointer\": \"\",\n \"parameter\": \"\"\n },\n \"code\": \"\",\n \"status\": \"\",\n \"meta\": {\n \"tracking_request_id\": \"\"\n }\n },\n {\n \"title\": \"\",\n \"detail\": \"\",\n \"source\": {\n \"pointer\": \"\",\n \"parameter\": \"\"\n },\n \"code\": \"\",\n \"status\": \"\",\n \"meta\": {\n \"tracking_request_id\": \"\"\n }\n }\n ]\n}", - "cookie": [], - "_postman_previewlanguage": "json" - } - ], - "event": [], - "protocolProfileBehavior": { - "disableBodyPruning": true - } - } - ] - } - ], - "auth": { - "type": "apikey", - "apikey": [ - { - "type": "any", - "value": "Authorization", - "key": "key" - }, - { - "type": "any", - "value": "{{apiKey}}", - "key": "value" - }, - { - "type": "any", - "value": "header", - "key": "in" - } - ] - }, - "event": [], - "variable": [ - { - "key": "baseUrl", - "value": "https://api.terminal49.com/v2" - } - ], - "info": { - "_postman_id": "874c6093-0196-4803-a8f2-15a318719379", - "name": "Terminal49 API Reference", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "description": { - "content": "The Terminal 49 API offers a convenient way to programmatically track your shipments from origin to destination.\n\nPlease enter your API key into the \"Variables\" tab before using these endpoints within Postman.\n\nContact Support:\n Name: Terminal49 API support\n Email: support@terminal49.com", - "type": "text/plain" - } - } -} \ No newline at end of file diff --git a/api.md b/api.md new file mode 100644 index 00000000..a7436b95 --- /dev/null +++ b/api.md @@ -0,0 +1,171 @@ +# Shipments + +Types: + +- Links +- Meta +- Shipment +- ShipmentRetrieveResponse +- ShipmentUpdateResponse +- ShipmentListResponse +- ShipmentResumeTrackingResponse +- ShipmentStopTrackingResponse + +Methods: + +- client.shipments.retrieve(id, { ...params }) -> ShipmentRetrieveResponse +- client.shipments.update(id, { ...params }) -> ShipmentUpdateResponse +- client.shipments.list({ ...params }) -> ShipmentListResponse +- client.shipments.resumeTracking(id) -> ShipmentResumeTrackingResponse +- client.shipments.stopTracking(id) -> ShipmentStopTrackingResponse + +# TrackingRequests + +Types: + +- Account +- TrackingRequest +- TrackingRequestCreateResponse +- TrackingRequestRetrieveResponse +- TrackingRequestUpdateResponse +- TrackingRequestListResponse + +Methods: + +- client.trackingRequests.create({ ...params }) -> TrackingRequestCreateResponse +- client.trackingRequests.retrieve(id, { ...params }) -> TrackingRequestRetrieveResponse +- client.trackingRequests.update(id, { ...params }) -> TrackingRequestUpdateResponse +- client.trackingRequests.list({ ...params }) -> TrackingRequestListResponse + +# Webhooks + +Types: + +- Webhook +- WebhookCreateResponse +- WebhookRetrieveResponse +- WebhookUpdateResponse +- WebhookListResponse +- WebhookListIPsResponse + +Methods: + +- client.webhooks.create({ ...params }) -> WebhookCreateResponse +- client.webhooks.retrieve(id) -> WebhookRetrieveResponse +- client.webhooks.update(id, { ...params }) -> WebhookUpdateResponse +- client.webhooks.list({ ...params }) -> WebhookListResponse +- client.webhooks.delete(id) -> void +- client.webhooks.listIPs() -> WebhookListIPsResponse + +# WebhookNotifications + +Types: + +- EstimatedEvent +- WebhookNotification +- WebhookNotificationRetrieveResponse +- WebhookNotificationListResponse +- WebhookNotificationGetExamplesResponse + +Methods: + +- client.webhookNotifications.retrieve(id, { ...params }) -> WebhookNotificationRetrieveResponse +- client.webhookNotifications.list({ ...params }) -> WebhookNotificationListResponse +- client.webhookNotifications.getExamples({ ...params }) -> WebhookNotificationGetExamplesResponse + +# Containers + +Types: + +- Container +- TransportEvent +- ContainerRetrieveResponse +- ContainerUpdateResponse +- ContainerListResponse +- ContainerGetRawEventsResponse +- ContainerGetTransportEventsResponse + +Methods: + +- client.containers.retrieve(id, { ...params }) -> ContainerRetrieveResponse +- client.containers.update({ ...params }) -> ContainerUpdateResponse +- client.containers.list({ ...params }) -> ContainerListResponse +- client.containers.getRawEvents(id) -> ContainerGetRawEventsResponse +- client.containers.getTransportEvents(id, { ...params }) -> ContainerGetTransportEventsResponse + +# ShippingLines + +Types: + +- ShippingLine +- ShippingLineRetrieveResponse +- ShippingLineListResponse + +Methods: + +- client.shippingLines.retrieve(id) -> ShippingLineRetrieveResponse +- client.shippingLines.list() -> ShippingLineListResponse + +# MetroAreas + +Types: + +- MetroArea +- MetroAreaRetrieveResponse + +Methods: + +- client.metroAreas.retrieve(id) -> MetroAreaRetrieveResponse + +# Ports + +Types: + +- Port +- PortRetrieveResponse + +Methods: + +- client.ports.retrieve(id) -> PortRetrieveResponse + +# Vessels + +Types: + +- Vessel +- VesselRetrieveByIDResponse +- VesselRetrieveByImoResponse + +Methods: + +- client.vessels.retrieveByID(id) -> VesselRetrieveByIDResponse +- client.vessels.retrieveByImo(imo) -> VesselRetrieveByImoResponse + +# Terminals + +Types: + +- Terminal +- TerminalRetrieveResponse + +Methods: + +- client.terminals.retrieve(id) -> TerminalRetrieveResponse + +# Parties + +Types: + +- LinkSelf +- Party +- PartyCreateResponse +- PartyRetrieveResponse +- PartyUpdateResponse +- PartyListResponse + +Methods: + +- client.parties.create({ ...params }) -> PartyCreateResponse +- client.parties.retrieve(id) -> PartyRetrieveResponse +- client.parties.update(id, { ...params }) -> PartyUpdateResponse +- client.parties.list({ ...params }) -> PartyListResponse diff --git a/assets/data/Terminal49_Shiping_Line_Support.csv b/assets/data/Terminal49_Shiping_Line_Support.csv deleted file mode 100644 index 5053b625..00000000 --- a/assets/data/Terminal49_Shiping_Line_Support.csv +++ /dev/null @@ -1,23 +0,0 @@ -Full Name,Nickname,SCAC,BOL Prefix,Support,Notes -American President Lines,APL,APLU,APLU,Yes, -Australia National Line,ANL,ANNU,ANLC,Yes, -China Ocean Shipping Company,COSCO,COSU,CCMJ,Yes, -CMA CGM,CMA CGM,CMDU,CMDU,Yes, -Evergreen,Evergreen,EGLV,EISU,Yes, -Hamburg Sud,Hamburg Sud,SUDU,SUDU,Yes, -Hapag-Lloyd,Hapag-Lloyd,HLCU,HLCU,Yes, -Hyundai Merchant Marine,Hyundai,HDMU,HDMU,Partial,No container numbers -Maersk,Maersk,MAEU,MAEU,Yes, -Matson Navigation Company,Matson,MATS,MATS,Yes, -Mediterranean Shipping Company,MSC,MSCU,MEDU,Yes, -Ocean Network Express,ONE,ONEY,ONEY,Yes, -Orient Overseas Container Line,OOCL,OOLU,OOLU,Yes, -Pacific International Lines,PIL,PCIU,PABV,Yes, -Safmarine Container Line,Safmarine,SAFM,SAFM,Yes, -SeaLand,SeaLand,SEAL,SEAU,Yes, -Yangming Marine Transport,Yangming,YMLU,YMLU,Yes, -Wan Hai Lines,Wan Hai,WHLC,,On roadmap,June Release -Matson,Matson,MATS,,On roadmap,June Release -Zim,Zim,ZIMU,,On roadmap,June Release -Seaboard Marine,Seaboard,CLAM,,On roadmap,June Release -Crowley,Crowley,CLAM,,On roadmap,June Release \ No newline at end of file diff --git a/assets/data/Terminal49_Terminal_Support.csv b/assets/data/Terminal49_Terminal_Support.csv deleted file mode 100644 index 54cc0e18..00000000 --- a/assets/data/Terminal49_Terminal_Support.csv +++ /dev/null @@ -1,47 +0,0 @@ -Terminal Name,Terminal Nickname,Firms Code,Port Name,Port Code,City,State,Country,Time Zone,Support -Dundalk Marine Terminal,Dundalk,A321,Baltimore,USBAL,Baltimore,MD,US,America/New_York,Yes -Seagirt Terminal,Seagirt,C324,Baltimore,USBAL,Baltimore,MD,US,America/New_York,Yes -Paul W Conley Terminal,Conley Terminal,A295,Boston,USBOS,Boston,MA,US,America/New_York,Yes -Barbours Cut Terminal,APM,S787,Houston,USHOU,Houston,TX,US,America/Chicago,Yes -Bayport Container Terminal,Bayport,V136,Houston,USHOU,Houston,TX,US,America/Chicago,Yes -SSA Cooper,SSA Cooper,N296,JAXPORT,USJAX,Jacksonville,FL,US,America/New_York,Yes -Trapac Jacksonville Terminal,Trapac Jacksonville,M029,JAXPORT,USJAX,Jacksonville,FL,US,America/New_York,Yes -Long Beach Container Terminal,LBCT Pier F,W183,Long Beach,USLGB,Los Angeles,CA,US,America/Los_Angeles,Yes -Long Beach Container Terminal,LBCT Pier E,WAC8,Long Beach,USLGB,Los Angeles,CA,US,America/Los_Angeles,Yes -Total Terminals International,TTI,Z952,Long Beach,USLGB,Los Angeles,CA,US,America/Los_Angeles,Yes -International Transportation Service,ITS,Y309,Long Beach,USLGB,Los Angeles,CA,US,America/Los_Angeles,Yes -Pacific Container Terminal,PCT,W182,Long Beach,USLGB,Los Angeles,CA,US,America/Los_Angeles,Yes -SSA Pier A,Pier A,Z978,Long Beach,USLGB,Los Angeles,CA,US,America/Los_Angeles,Yes -APM Terminals,APMT,W185,Los Angeles,USLAX,Los Angeles,CA,US,America/Los_Angeles,Yes -Fenix Marine Services,FMS,Y257,Los Angeles,USLAX,Los Angeles,CA,US,America/Los_Angeles,Yes -West Basin Container Terminal,WBCT,Y773,Los Angeles,USLAX,Los Angeles,CA,US,America/Los_Angeles,Yes -Everport Terminal Serivces,ETS,Y124,Los Angeles,USLAX,Los Angeles,CA,US,America/Los_Angeles,Yes -TraPac LA,TraPac LA,Y258,Los Angeles,USLAX,Los Angeles,CA,US,America/Los_Angeles,Yes -Yusen Terminals,Yusen,Y790,Los Angeles,USLAX,Los Angeles,CA,US,America/Los_Angeles,Yes -Port of Miami Terminal Operating Company,POMTOC,L239,Miami Seaport,USMIA,Miami,FL,US,America/New_York,Yes -South Florida Container Terminal,SFCT,N775,Miami Seaport,USMIA,Miami,FL,US,America/New_York,Yes -APM Terminal,APM Mobile,R103,Mobile,USMOB,Mobile,AL,US,America/Chicago,Yes -Napoleon Container Terminal,Napoleon,P260,New Orleans,USMSY,New Orleans,LA,US,America/Chicago,Yes -New Orleans Terminal,Milan,Q795,New Orleans,USMSY,New Orleans,LA,US,America/Chicago,Yes -Port Elizabeth,APM Terminals,E425,New York / New Jersey,USNYC,New York,NY,US,America/New_York,Yes -GCT New York LP,GCT,E005,New York / New Jersey,USNYC,New York,NY,US,America/New_York,Yes -Maher Terminals,Maher,E416,New York / New Jersey,USNYC,New York,NY,US,America/New_York,Yes -GCT Bayonne,GCTB,E364,New York / New Jersey,USNYC,New York,NY,US,America/New_York,Yes -Port Newark Container Terminal,PNCT,F577,New York / New Jersey,USNYC,New York,NY,US,America/New_York,Yes -Oakland International Container Terminal,SSA,Z985,Oakland,USOAK,Oakland,CA,US,America/Los_Angeles,Yes -TraPac Terminal,TraPac,Y549,Oakland,USOAK,Oakland,CA,US,America/Los_Angeles,Yes -Ben E. Nutter Terminal,Everport,Y738,Oakland,USOAK,Oakland,CA,US,America/Los_Angeles,Yes -Packer Avenue,Packer Avenue,C095,PhilaPort,USPHL,Philadelphia,PA,US,America/New_York,Yes -Florida International Terminal,FIT,N751,Port Everglades,USPEF,Fort Lauderdale,FL,US,America/New_York,Yes -Tampa Bay Container Terminal,Tampa Bay,M669,Port Tampa Bay,USPOT,Tampa Bay,FL,US,America/New_York,Yes -Garden City Terminals,GCT,L737,Savannah,USSAV,Savannah,GA,US,America/New_York,Yes -SSA Terminal 18,Terminal 18,X117,Seattle,USSEA,Seattle,WA,US,America/Los_Angeles,Yes -SSA Terminal 30,Terminal 30,Y197,Seattle,USSEA,Seattle,WA,US,America/Los_Angeles,Yes -Pierce County Terminal,PCT,X215,Tacoma,USTIW,Tacoma,WA,US,America/Los_Angeles,Yes -Husky Terminal,Husky,Z693,Tacoma,USTIW,Tacoma,WA,US,America/Los_Angeles,Yes -Washington United Terminals,WUT,Z705,Tacoma,USTIW,Tacoma,WA,US,America/Los_Angeles,Yes -Tacoma Container Terminal,TCT,WAM2,Tacoma,USTIW,Tacoma,WA,US,America/Los_Angeles,Yes -Richmond Marine Terminal,Richmond (RMT),LAQ4,Virginia,USORF,Norfolk,VA,US,America/New_York,Yes -Portsmouth Marine Terminal,Portsmouth (PMT),L045,Virginia,USORF,Norfolk,VA,US,America/New_York,Yes -Virginia International Gateway,Virginia (VIG),N195,Virginia,USORF,Norfolk,VA,US,America/New_York,Yes -Norfolk International Terminals,Norfolk (NIT),L005,Virginia,USORF,Norfolk,VA,US,America/New_York,Yes \ No newline at end of file diff --git a/assets/images/1pgr Final Drafts (1).png b/assets/images/1pgr Final Drafts (1).png deleted file mode 100644 index e1401585..00000000 Binary files a/assets/images/1pgr Final Drafts (1).png and /dev/null differ diff --git "a/assets/images/Screenshot 2024-04-11 at 10.47.23\342\200\257AM.png" "b/assets/images/Screenshot 2024-04-11 at 10.47.23\342\200\257AM.png" deleted file mode 100644 index 242bf70b..00000000 Binary files "a/assets/images/Screenshot 2024-04-11 at 10.47.23\342\200\257AM.png" and /dev/null differ diff --git "a/assets/images/Screenshot 2024-04-11 at 10.50.11\342\200\257AM.png" "b/assets/images/Screenshot 2024-04-11 at 10.50.11\342\200\257AM.png" deleted file mode 100644 index 67fc1727..00000000 Binary files "a/assets/images/Screenshot 2024-04-11 at 10.50.11\342\200\257AM.png" and /dev/null differ diff --git a/assets/images/carriers_screenshot.png b/assets/images/carriers_screenshot.png deleted file mode 100644 index 94f49141..00000000 Binary files a/assets/images/carriers_screenshot.png and /dev/null differ diff --git a/assets/images/create-shipment-flow.png b/assets/images/create-shipment-flow.png deleted file mode 100644 index e06142e6..00000000 Binary files a/assets/images/create-shipment-flow.png and /dev/null differ diff --git a/assets/images/map/terminal49-map-colors.png b/assets/images/map/terminal49-map-colors.png deleted file mode 100644 index c4b4d0ad..00000000 Binary files a/assets/images/map/terminal49-map-colors.png and /dev/null differ diff --git a/assets/images/map/terminal49-map-expanded.png b/assets/images/map/terminal49-map-expanded.png deleted file mode 100644 index 0621cc46..00000000 Binary files a/assets/images/map/terminal49-map-expanded.png and /dev/null differ diff --git a/assets/images/map/terminal49-map.png b/assets/images/map/terminal49-map.png deleted file mode 100644 index 3eb1be96..00000000 Binary files a/assets/images/map/terminal49-map.png and /dev/null differ diff --git a/assets/images/new_webhook.png b/assets/images/new_webhook.png deleted file mode 100644 index c94341de..00000000 Binary files a/assets/images/new_webhook.png and /dev/null differ diff --git a/assets/images/terminal49-container-tracking-widget.jpg b/assets/images/terminal49-container-tracking-widget.jpg deleted file mode 100644 index f01d7b56..00000000 Binary files a/assets/images/terminal49-container-tracking-widget.jpg and /dev/null differ diff --git a/bin/check-release-environment b/bin/check-release-environment new file mode 100644 index 00000000..6b43775a --- /dev/null +++ b/bin/check-release-environment @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +errors=() + +lenErrors=${#errors[@]} + +if [[ lenErrors -gt 0 ]]; then + echo -e "Found the following errors in the release environment:\n" + + for error in "${errors[@]}"; do + echo -e "- $error\n" + done + + exit 1 +fi + +echo "The environment is ready to push releases!" + diff --git a/bin/publish-npm b/bin/publish-npm new file mode 100644 index 00000000..45e8aa80 --- /dev/null +++ b/bin/publish-npm @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +set -eux + +npm config set '//registry.npmjs.org/:_authToken' "$NPM_TOKEN" + +yarn build +cd dist + +# Get package name and version from package.json +PACKAGE_NAME="$(jq -r -e '.name' ./package.json)" +VERSION="$(jq -r -e '.version' ./package.json)" + +# Get latest version from npm +# +# If the package doesn't exist, npm will return: +# { +# "error": { +# "code": "E404", +# "summary": "Unpublished on 2025-06-05T09:54:53.528Z", +# "detail": "'the_package' is not in this registry..." +# } +# } +NPM_INFO="$(npm view "$PACKAGE_NAME" version --json 2>/dev/null || true)" + +# Check if we got an E404 error +if echo "$NPM_INFO" | jq -e '.error.code == "E404"' > /dev/null 2>&1; then + # Package doesn't exist yet, no last version + LAST_VERSION="" +elif echo "$NPM_INFO" | jq -e '.error' > /dev/null 2>&1; then + # Report other errors + echo "ERROR: npm returned unexpected data:" + echo "$NPM_INFO" + exit 1 +else + # Success - get the version + LAST_VERSION=$(echo "$NPM_INFO" | jq -r '.') # strip quotes +fi + +# Check if current version is pre-release (e.g. alpha / beta / rc) +CURRENT_IS_PRERELEASE=false +if [[ "$VERSION" =~ -([a-zA-Z]+) ]]; then + CURRENT_IS_PRERELEASE=true + CURRENT_TAG="${BASH_REMATCH[1]}" +fi + +# Check if last version is a stable release +LAST_IS_STABLE_RELEASE=true +if [[ -z "$LAST_VERSION" || "$LAST_VERSION" =~ -([a-zA-Z]+) ]]; then + LAST_IS_STABLE_RELEASE=false +fi + +# Use a corresponding alpha/beta tag if there already is a stable release and we're publishing a prerelease. +if $CURRENT_IS_PRERELEASE && $LAST_IS_STABLE_RELEASE; then + TAG="$CURRENT_TAG" +else + TAG="latest" +fi + +# Publish with the appropriate tag +yarn publish --tag "$TAG" diff --git a/docs/api-docs/api-reference/containers/edit-a-container.mdx b/docs/api-docs/api-reference/containers/edit-a-container.mdx deleted file mode 100644 index 676e373f..00000000 --- a/docs/api-docs/api-reference/containers/edit-a-container.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Edit a Container | Terminal49 API Documentation -og:description: Learn how to edit container details through Terminal49's API. Update container info efficiently and integrate changes into your shipping workflows. -openapi: patch /containers ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/containers/get-a-container.mdx b/docs/api-docs/api-reference/containers/get-a-container.mdx deleted file mode 100644 index 73acd37b..00000000 --- a/docs/api-docs/api-reference/containers/get-a-container.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Get a Container | Terminal49 API Documentation -og:description: Retrieve comprehensive container information using Terminal49's API to streamline logistics management and enhance data accuracy. -openapi: get /containers/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/containers/get-a-containers-raw-events.mdx b/docs/api-docs/api-reference/containers/get-a-containers-raw-events.mdx deleted file mode 100644 index cec40ab4..00000000 --- a/docs/api-docs/api-reference/containers/get-a-containers-raw-events.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Access Raw Container Events | Terminal49 API Documentation -og:description: Access raw event data for containers via Terminal49's API to track shipment progress and optimize your supply chain. -openapi: get /containers/{id}/raw_events ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/containers/get-a-containers-transport-events.mdx b/docs/api-docs/api-reference/containers/get-a-containers-transport-events.mdx deleted file mode 100644 index dad79b09..00000000 --- a/docs/api-docs/api-reference/containers/get-a-containers-transport-events.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Retrieve Transport Events | Terminal49 API Documentation -og:description: Fetch detailed transport event data for containers, providing real-time updates on shipment status through Terminal49's API. -openapi: get /containers/{id}/transport_events ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/containers/get-container-route.mdx b/docs/api-docs/api-reference/containers/get-container-route.mdx deleted file mode 100644 index e658e6fb..00000000 --- a/docs/api-docs/api-reference/containers/get-container-route.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Get container route | Terminal49 API Documentation -og:description: Retrieves the route details for a container including all port calls and vessel changes. -openapi: get /containers/{id}/route ---- diff --git a/docs/api-docs/api-reference/containers/list-containers.mdx b/docs/api-docs/api-reference/containers/list-containers.mdx deleted file mode 100644 index d998dacb..00000000 --- a/docs/api-docs/api-reference/containers/list-containers.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: List Containers | Terminal49 API Documentation -og:description: Use Terminal49's API to list all containers associated with your account. Stay organized with a clear overview of all shipments. -openapi: get /containers ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/containers/refresh-container.mdx b/docs/api-docs/api-reference/containers/refresh-container.mdx deleted file mode 100644 index a6c9d33f..00000000 --- a/docs/api-docs/api-reference/containers/refresh-container.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Refresh Container | Terminal49 API Documentation -og:description: Trigger immediate container data refresh from all sources using Terminal49's API. Real-time updates for enhanced shipment tracking and visibility. -openapi: patch /containers/{id}/refresh ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/metro-areas/get-a-metro-area-using-the-unlocode-or-the-id.mdx b/docs/api-docs/api-reference/metro-areas/get-a-metro-area-using-the-unlocode-or-the-id.mdx deleted file mode 100644 index f4ab4785..00000000 --- a/docs/api-docs/api-reference/metro-areas/get-a-metro-area-using-the-unlocode-or-the-id.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Get Metro Area by UNLOCODE or ID | Terminal49 API -og:description: Retrieve detailed metro area information using the UNLOCODE or ID, enhancing geographic data management with Terminal49's API. -openapi: get /metro_areas/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/parties/create-a-party.mdx b/docs/api-docs/api-reference/parties/create-a-party.mdx deleted file mode 100644 index f3148dd5..00000000 --- a/docs/api-docs/api-reference/parties/create-a-party.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Create Party | Terminal49 API Documentation -og:description: Create a party via Terminal49's API for tracking requests. -openapi: post /parties ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/parties/edit-a-party.mdx b/docs/api-docs/api-reference/parties/edit-a-party.mdx deleted file mode 100644 index 332e3184..00000000 --- a/docs/api-docs/api-reference/parties/edit-a-party.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Edit Party details by ID | Terminal49 API Documentation -og:description: Update party information using Terminal49's API. -openapi: patch /parties/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/parties/get-a-party.mdx b/docs/api-docs/api-reference/parties/get-a-party.mdx deleted file mode 100644 index dde64bc2..00000000 --- a/docs/api-docs/api-reference/parties/get-a-party.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Get a Party by ID | Terminal49 API Documentation -og:description: Retrieve a party information using Terminal49's API. -openapi: get /parties/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/parties/list-parties.mdx b/docs/api-docs/api-reference/parties/list-parties.mdx deleted file mode 100644 index 7aaee65d..00000000 --- a/docs/api-docs/api-reference/parties/list-parties.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: List all Parties | Terminal49 API Documentation -og:description: Retrieve a list of all parties using Terminal49's API. -openapi: get /parties ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/ports/get-a-port-using-the-locode-or-the-id.mdx b/docs/api-docs/api-reference/ports/get-a-port-using-the-locode-or-the-id.mdx deleted file mode 100644 index 7e64043a..00000000 --- a/docs/api-docs/api-reference/ports/get-a-port-using-the-locode-or-the-id.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Access Port Details by LOCODE or ID | Terminal49 API -og:description: Retrieve port data via LOCODE or ID, streamlining port-related operations and integrating key location data with Terminal49's API. -openapi: get /ports/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/shipments/edit-a-shipment.mdx b/docs/api-docs/api-reference/shipments/edit-a-shipment.mdx deleted file mode 100644 index 7a997d08..00000000 --- a/docs/api-docs/api-reference/shipments/edit-a-shipment.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Edit a Shipment | Terminal49 API Documentation -og:description: Modify shipment information quickly and accurately using Terminal49's API to ensure up-to-date logistics and tracking data. -openapi: patch /shipments/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/shipments/get-a-shipment.mdx b/docs/api-docs/api-reference/shipments/get-a-shipment.mdx deleted file mode 100644 index 0870150f..00000000 --- a/docs/api-docs/api-reference/shipments/get-a-shipment.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Get a Shipment | Terminal49 API Documentation -og:description: Retrieve shipment details quickly with Terminal49's API. Enhance your shipping visibility and streamline tracking processes. -openapi: get /shipments/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/shipments/list-shipments.mdx b/docs/api-docs/api-reference/shipments/list-shipments.mdx deleted file mode 100644 index 2962b28e..00000000 --- a/docs/api-docs/api-reference/shipments/list-shipments.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: List Shipments | Terminal49 API Documentation -og:description: List all shipment details using Terminal49's API. Improve operational efficiency with organized shipment overviews. -openapi: get /shipments ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/shipments/resume-tracking-shipment.mdx b/docs/api-docs/api-reference/shipments/resume-tracking-shipment.mdx deleted file mode 100644 index 5e0ddc76..00000000 --- a/docs/api-docs/api-reference/shipments/resume-tracking-shipment.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Resume Shipment Tracking | Terminal49 API Documentation -og:description: Resume tracking for shipments using Terminal49's API. Keep tabs on crucial shipments with automated updates. -openapi: patch /shipments/{id}/resume_tracking ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/shipments/stop-tracking-shipment.mdx b/docs/api-docs/api-reference/shipments/stop-tracking-shipment.mdx deleted file mode 100644 index 456fc77e..00000000 --- a/docs/api-docs/api-reference/shipments/stop-tracking-shipment.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Stop Shipment Tracking | Terminal49 API Documentation -og:description: Stop shipment tracking anytime via Terminal49's API. Manage tracking effortlessly when deliveries are complete. -openapi: patch /shipments/{id}/stop_tracking ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/shipping-lines/get-a-single-shipping-line.mdx b/docs/api-docs/api-reference/shipping-lines/get-a-single-shipping-line.mdx deleted file mode 100644 index 0db82546..00000000 --- a/docs/api-docs/api-reference/shipping-lines/get-a-single-shipping-line.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Get a Shipping Line | Terminal49 API Documentation -og:description: Retrieve information about shipping lines with Terminal49's API. Stay updated with accurate shipping line details. -openapi: get /shipping_lines/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/shipping-lines/shipping-lines.mdx b/docs/api-docs/api-reference/shipping-lines/shipping-lines.mdx deleted file mode 100644 index b72a13d8..00000000 --- a/docs/api-docs/api-reference/shipping-lines/shipping-lines.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Shipping Lines Overview | Terminal49 API Documentation -og:description: Get an overview of all shipping lines using Terminal49's API. Streamline logistics with detailed shipping line data. -openapi: get /shipping_lines ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/terminals/get-a-terminal-using-the-id.mdx b/docs/api-docs/api-reference/terminals/get-a-terminal-using-the-id.mdx deleted file mode 100644 index 814ada4b..00000000 --- a/docs/api-docs/api-reference/terminals/get-a-terminal-using-the-id.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Get Terminal by ID | Terminal49 API Documentation -og:description: Retrieve terminal information using Terminal49's API. Access up-to-date terminal details by ID quickly. -openapi: get /terminals/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx b/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx deleted file mode 100644 index 7b804f03..00000000 --- a/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Create a Tracking Request | Terminal49 API Documentation -og:description: Create tracking requests via Terminal49's API for accurate shipment tracking and notifications. -openapi: post /tracking_requests ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/tracking-requests/edit-a-tracking-request.mdx b/docs/api-docs/api-reference/tracking-requests/edit-a-tracking-request.mdx deleted file mode 100644 index 5aefd63e..00000000 --- a/docs/api-docs/api-reference/tracking-requests/edit-a-tracking-request.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: patch /tracking_requests/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/tracking-requests/get-a-single-tracking-request.mdx b/docs/api-docs/api-reference/tracking-requests/get-a-single-tracking-request.mdx deleted file mode 100644 index 11eb8c12..00000000 --- a/docs/api-docs/api-reference/tracking-requests/get-a-single-tracking-request.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: get /tracking_requests/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/tracking-requests/list-tracking-requests.mdx b/docs/api-docs/api-reference/tracking-requests/list-tracking-requests.mdx deleted file mode 100644 index e535e924..00000000 --- a/docs/api-docs/api-reference/tracking-requests/list-tracking-requests.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: get /tracking_requests ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/vessels/get-a-vessel-using-the-id.mdx b/docs/api-docs/api-reference/vessels/get-a-vessel-using-the-id.mdx deleted file mode 100644 index bffc99e8..00000000 --- a/docs/api-docs/api-reference/vessels/get-a-vessel-using-the-id.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: get /vessels/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/vessels/get-a-vessel-using-the-imo.mdx b/docs/api-docs/api-reference/vessels/get-a-vessel-using-the-imo.mdx deleted file mode 100644 index 6ae9a90d..00000000 --- a/docs/api-docs/api-reference/vessels/get-a-vessel-using-the-imo.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: get /vessels/{imo} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/vessels/get-vessel-future-positions-with-coordinates.mdx b/docs/api-docs/api-reference/vessels/get-vessel-future-positions-with-coordinates.mdx deleted file mode 100644 index 6603053e..00000000 --- a/docs/api-docs/api-reference/vessels/get-vessel-future-positions-with-coordinates.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Get vessel future positions with coordinates | Terminal49 API Documentation -og:description: Returns estimated future positions for a vessel based on specific coordinates and destination ports. -openapi: get /vessels/{id}/future_positions_with_coordinates ---- diff --git a/docs/api-docs/api-reference/vessels/get-vessel-future-positions.mdx b/docs/api-docs/api-reference/vessels/get-vessel-future-positions.mdx deleted file mode 100644 index 7e8663e3..00000000 --- a/docs/api-docs/api-reference/vessels/get-vessel-future-positions.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Get vessel future positions | Terminal49 API Documentation -og:description: Returns estimated future positions for a vessel based on its current location and destination ports. -openapi: get /vessels/{id}/future_positions ---- diff --git a/docs/api-docs/api-reference/webhook-notifications/get-a-single-webhook-notification.mdx b/docs/api-docs/api-reference/webhook-notifications/get-a-single-webhook-notification.mdx deleted file mode 100644 index 7724627a..00000000 --- a/docs/api-docs/api-reference/webhook-notifications/get-a-single-webhook-notification.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: get /webhook_notifications/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/webhook-notifications/get-webhook-notification-payload-examples.mdx b/docs/api-docs/api-reference/webhook-notifications/get-webhook-notification-payload-examples.mdx deleted file mode 100644 index 4c13472a..00000000 --- a/docs/api-docs/api-reference/webhook-notifications/get-webhook-notification-payload-examples.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: get /webhook_notifications/examples ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/webhook-notifications/list-webhook-notifications.mdx b/docs/api-docs/api-reference/webhook-notifications/list-webhook-notifications.mdx deleted file mode 100644 index c6025ba8..00000000 --- a/docs/api-docs/api-reference/webhook-notifications/list-webhook-notifications.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: get /webhook_notifications ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/webhooks/create-a-webhook.mdx b/docs/api-docs/api-reference/webhooks/create-a-webhook.mdx deleted file mode 100644 index 6819209a..00000000 --- a/docs/api-docs/api-reference/webhooks/create-a-webhook.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: post /webhooks ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/webhooks/delete-a-webhook.mdx b/docs/api-docs/api-reference/webhooks/delete-a-webhook.mdx deleted file mode 100644 index 6aed4032..00000000 --- a/docs/api-docs/api-reference/webhooks/delete-a-webhook.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: delete /webhooks/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/webhooks/edit-a-webhook.mdx b/docs/api-docs/api-reference/webhooks/edit-a-webhook.mdx deleted file mode 100644 index 1ae1d70b..00000000 --- a/docs/api-docs/api-reference/webhooks/edit-a-webhook.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Edit a Webhook | Terminal49 API Documentation -og:description: Modify webhook settings with Terminal49's API. Ensure accurate notifications for container tracking. -openapi: patch /webhooks/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/webhooks/get-single-webhook.mdx b/docs/api-docs/api-reference/webhooks/get-single-webhook.mdx deleted file mode 100644 index bfd4fdca..00000000 --- a/docs/api-docs/api-reference/webhooks/get-single-webhook.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: Get Single Webhook | Terminal49 API Documentation -og:description: Retrieve a single webhook using Terminal49's API for detailed event tracking. -openapi: get /webhooks/{id} ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/webhooks/list-webhook-ips.mdx b/docs/api-docs/api-reference/webhooks/list-webhook-ips.mdx deleted file mode 100644 index 30eeb12e..00000000 --- a/docs/api-docs/api-reference/webhooks/list-webhook-ips.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: List Webhook IPs | Terminal49 API Documentation -og:description: List webhook IPs using Terminal49's API for improved network security and configuration. -openapi: get /webhooks/ips ---- \ No newline at end of file diff --git a/docs/api-docs/api-reference/webhooks/list-webhooks.mdx b/docs/api-docs/api-reference/webhooks/list-webhooks.mdx deleted file mode 100644 index a4990f42..00000000 --- a/docs/api-docs/api-reference/webhooks/list-webhooks.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -og:title: List Webhooks | Terminal49 API Documentation -og:description: View all webhooks associated with your account using Terminal49's API for real-time event tracking. -openapi: get /webhooks ---- \ No newline at end of file diff --git a/docs/api-docs/getting-started/list-shipments-and-containers.mdx b/docs/api-docs/getting-started/list-shipments-and-containers.mdx deleted file mode 100644 index fdcc53ff..00000000 --- a/docs/api-docs/getting-started/list-shipments-and-containers.mdx +++ /dev/null @@ -1,281 +0,0 @@ ---- -title: 3. List Your Shipments & Containers ---- -## Shipment and Container Data in Terminal49 - -After you've successfully made a tracking request, Terminal49 will begin to track shipments and store relevant information about that shipment on your behalf. - -The initial tracking request starts this process, collecting available data from Carriers and Terminals. Then, Terminal49 periodically checks for new updates adn pulls data from the carriers and terminals to keep the data we store up to date. - -You can access data about shipments and containers on your tracked shipments any time. We will introduce the basics of this method below. - -Keep in mind, however, that apart from initialization code, you would not usually access shipment data in this way. You would use Webhooks (described in the next section). A Webhook is another name for a web-based callback URL, or a HTTP Push API. They provide a method for an API to post a notification to your service. Specifically, a webhook is simply a URL that can receive HTTP Post Requests from the Terminal49 API. - -## List all your Tracked Shipments - -If your tracking request was successful, you will now be able to list your tracked shipments. - -**Try it below. Click "Headers" and replace YOUR_API_KEY with your API key.** - -Sometimes it may take a while for the tracking request to show up, but usually no more than a few minutes. - -If you had trouble adding your first shipment, try adding a few more. - -**We suggest copy and pasting the response returned into a text editor so you can examine it while continuing the tutorial.** - -```json http -{ - "method": "get", - "url": "https://api.terminal49.com/v2/shipments", - "headers": { - "Content-Type": "application/vnd.api+json", - "Authorization": "Token YOUR_API_KEY" - } -} -``` - - -> ### Why so much JSON? (A note on JSON API) ->The Terminal49 API is JSON API compliant, which means that there are nifty libraries which can translate JSON into a fully fledged object model that can be used with an ORM. This is very powerful, but it also requires a larger, more structured payload to power the framework. The tradeoff, therefore, is that it's less convenient if you're parsing the JSON directly. Ultimately we strongly recommend you set yourself up with a good library to use JSON API to its fullest extent. But for the purposes of understanding the API's fundamentals and getting your feet wet, we'll work with the data directly. - -## Authentication - -The API uses HTTP Bearer Token authentication. - -This means you send your API Key as your token in every request. - -Webhooks are associated with API tokens, and this is how the Terminal49 knows who to return relevant shipment information to. - - -## Anatomy of Shipments JSON Response - -Here's what you'll see come back after you get the /shipments endpoint. - -Note that for clarity I've deleted some of the data that is less useful right now, and replaced them with ellipses (...). Bolded areas are also mine to point out important data. - -The **Data** attribute contains an array of objects. Each object is of type "shipment" and includes attributes such as bill of lading number, the port of lading, and so forth. Each Shipment object also has Relationships to structured data objects, for example, Ports and Terminals, as well as a list of Containers which are on this shipment. - -You can write code to access these structured elements from the API. The advantage of this approach is that Terminal49 cleans and enhances the data that is provided from the steamship line, meaning that you can access a pre-defined object definition for a specific port in Los Angeles. - - -```jsx -{ - "data": [ - { - /* this is an internal id that you can use to query the API directly, i.e by hitting https://api.terminal49.com/v2/shipments/123456789 */ - "id": "123456789", - // the object type is a shipment, per below. - "type": "shipment", - "attributes": { - // Your BOL number that you used in the tracking request - "bill_of_lading_number": "99999999", - ... - "shipping_line_scac": "MAEU", - "shipping_line_name": "Maersk", - "port_of_lading_locode": "INVTZ", - "port_of_lading_name": "Visakhapatnam", - ... - }, - "relationships": { - - "port_of_lading": { - "data": { - "id": "bde5465a-1160-4fde-a026-74df9c362f65", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "3d892622-def8-4155-94c5-91d91dc42219", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "99e1f6ba-a514-4355-8517-b4720bdc5f33", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "containers": { - "data": [ - { - "id": "593f3782-cc24-46a9-a6ce-b2f1dbf3b6b9", - "type": "container" - } - ] - } - }, - "links": { - // this is a link to this specific shipment in the API. - "self": "/v2/shipments/7f8c52b2-c255-4252-8a82-f279061fc847" - } - }, - ... - ], - ... -} -``` - -## Sample Code: Listing Tracked Shipment into a Google Sheet - -Below is code written in Google App Script that lists the current shipments into the current sheet of a spreadsheet. App Script is very similar to Javascript. - -Because Google App Script does not have native JSON API support, we need to parse the JSON directly, making this example an ideal real world application of the API. - - -```jsx - -function listTrackedShipments(){ - // first we construct the request. - var options = { - "method" : "GET", - "headers" : { - "content-type": "application/vnd.api+json", - "authorization" : "Token YOUR_API_KEY" - }, - "payload" : "" - }; - - - try { - // note that URLFetchApp is a function of Google App Script, not a standard JS function. - var response = UrlFetchApp.fetch("https://api.terminal49.com/v2/shipments", options); - var json = response.getContentText(); - var shipments = JSON.parse(json)["data"]; - var shipment_values = []; - shipment_values = extractShipmentValues(shipments); - listShipmentValues(shipment_values); - } catch (error){ - //In JS you would use console.log(), but App Script uses Logger.log(). - Logger.log("error communicating with t49 / shipments: " + error); - } -} - - -function extractShipmentValues(shipments){ - var shipment_values = []; - shipments.forEach(function(shipment){ - // iterating through the shipments. - shipment_values.push(extractShipmentData(shipment)); - }); - return shipment_values; -} - -function extractShipmentData(shipment){ - var shipment_val = []; - //for each shipment I'm extracting some of the key info i want to display. - shipment_val.push(shipment["attributes"]["shipping_line_scac"], - shipment["attributes"]["shipping_line_name"], - shipment["attributes"]["bill_of_lading_number"], - shipment["attributes"]["pod_vessel_name"], - shipment["attributes"]["port_of_lading_name"], - shipment["attributes"]["pol_etd_at"], - shipment["attributes"]["pol_atd_at"], - shipment["attributes"]["port_of_discharge_name"], - shipment["attributes"]["pod_eta_at"], - shipment["attributes"]["pod_ata_at"], - shipment["relationships"]["containers"]["data"].length, - shipment["id"] - ); - return shipment_val; -} - - -function listShipmentValues(shipment_values){ -// now, list the data in the spreadsheet. - var ss = SpreadsheetApp.getActiveSpreadsheet(); - var homesheet = ss.getActiveSheet(); - var STARTING_ROW = 1; - var MAX_TRACKED = 500; - try { - // clear the contents of the sheet first. - homesheet.getRange(STARTING_ROW,1,MAX_TRACKED,shipment_values[0].length).clearContent(); - // now insert all the shipment values directly into the sheet. - homesheet.getRange(STARTING_ROW,1,shipment_values.length,shipment_values[0].length).setValues(shipment_values); - } catch (error){ - Logger.log("there was an error in listShipmentValues: " + error); - } -} -``` - - -## List all your Tracked Containers - -You can also list out all of your Containers. Container data includes Terminal availability, last free day, and other logistical information that you might use for drayage operations at port. - -**Try it below. Click "Headers" and replace YOUR_API_KEY with your API key.** - -**We suggest copy and pasting the response returned into a text editor so you can examine it while continuing the tutorial.** - - -```json http -{ - "method": "get", - "url": "https://api.terminal49.com/v2/containers", - "headers": { - "Content-Type": "application/vnd.api+json", - "Authorization": "Token YOUR_API_KEY" - } -} -``` - -## Anatomy of Containers JSON Response -Now that you've got a list of containers, let's examine the response you've received. - -```jsx -// We have an array of objects in the data returned. - "data": [ - { - // - "id": "internalid", - // this object is of type Container. - "type": "container", - "attributes": { - - // Here is your container number - "number": "OOLU-xxxx", - // Seal Numbers aren't always returned by the carrier. - "seal_number": null, - "created_at": "2020-09-13T19:16:47Z", - "equipment_type": "reefer", - "equipment_length": null, - "equipment_height": null, - "weight_in_lbs": 54807, - - //currently no known fees; this list will expand. - "fees_at_pod_terminal": [], - "holds_at_pod_terminal": [], - // here is your last free day. - "pickup_lfd": "2020-09-17T07:00:00Z", - "pickup_appointment_at": null, - "availability_known": true, - "available_for_pickup": false, - "pod_arrived_at": "2020-09-13T22:05:00Z", - "pod_discharged_at": "2020-09-15T05:27:00Z", - "location_at_pod_terminal": "CC1-162-B-3(Deck)", - "final_destination_full_out_at": null, - "pod_full_out_at": "2020-09-18T10:30:00Z", - "empty_terminated_at": null - }, - "relationships": { - // linking back to the shipment object, found above. - "shipment": { - "data": { - "id": "894befec-e7e2-4e48-ab97-xxxxxxxxx", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "39d09f18-cf98-445b-b6dc-xxxxxxxxx", - "type": "terminal" - } - }, - ... - } - }, - ... -``` \ No newline at end of file diff --git a/docs/api-docs/getting-started/receive-status-updates.mdx b/docs/api-docs/getting-started/receive-status-updates.mdx deleted file mode 100644 index 0b350971..00000000 --- a/docs/api-docs/getting-started/receive-status-updates.mdx +++ /dev/null @@ -1,318 +0,0 @@ ---- -title: 4. How to Receive Status Updates -og:title: Receive Status Updates | Terminal49 API Documentation -og:description: Get real-time status updates on shipments using Terminal49's API. Ensure timely deliveries with accurate tracking data. ---- - -## Using Webhooks to Receive Status Updates - -Terminal49 posts status updates to a webhook that you register with us. - -A Webhook is another name for a web-based callback URL, or a HTTP Push API. They provide a method for an API to post a notification to your service. Specifically, a webhook is simply a URL that can receive HTTP Post Requests from the Terminal49 API. - -The HTTP Post request from Terminal49 has a JSON payload which you can parse to extract the relevant information. - -## How do I use a Webhook with Terminal49? - -First, you need to register a webhook. You can register as many webhooks as you like. Webhooks are associated with your account. All updates relating to that account are sent to the Webhook associated with it. - -You can setup a new webhook by visiting https://app.terminal49.com/developers/webhooks and clicking the 'Create Webhook Endpoint' button. - -![Webhook Editing Screen](https://raw.githubusercontent.com/Terminal49/t49-api-documentation/master/assets/images/new_webhook.png "Webhook Editing Screen") - - -## Authentication - -The API uses HTTP Bearer Token authentication. - -This means you send your API Key as your token in every request. - -Webhooks are associated with API tokens, and this is how the Terminal49 knows who to return relevant shipment information to. - -## Anatomy of a Webhook Notification - -Here's what you'll see in a Webhook Notification, which arrives as a POST request to your designated URL. - -For more information, refer to the Webhook In Depth guide. - -Note that for clarity I've deleted some of the data that is less useful right now, and replaced them with ellipses (...). Bolded areas are also mine to point out important data. - -Note that there are two main sections: - -**Data.** The core information being returned. - -**Included**. Included are relevant objects that you are included for convenience. - -```jsx -{ - "data": { - "id": "87d4f5e3-df7b-4725-85a3-b80acc572e5d", - "type": "webhook_notification", - "attributes": { - "id": "87d4f5e3-df7b-4725-85a3-b80acc572e5d", - "event": "tracking_request.succeeded", - "delivery_status": "pending", - "created_at": "2020-09-13 14:46:37 UTC" - }, - "relationships": { - ... - } - }, - "included":[ - { - "id": "90873f19-f9e8-462d-b129-37e3d3b64c82", - "type": "tracking_request", - "attributes": { - "request_number": "MEDUNXXXXXX", - ... - }, - ... - }, - { - "id": "66db1d2a-eaa1-4f22-ba8d-0c41b051c411", - "type": "shipment", - "attributes": { - "created_at": "2020-09-13 14:46:36 UTC", - "bill_of_lading_number": "MEDUNXXXXXX", - "ref_numbers":[ - null - ], - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "port_of_lading_locode": "PLGDY", - "port_of_lading_name": "Gdynia", - .... - }, - "relationships": { - ... - }, - "links": { - "self": "/v2/shipments/66db1d2a-eaa1-4f22-ba8d-0c41b051c411" - } - }, - { - "id": "4d556105-015e-4c75-94a9-59cb8c272148", - "type": "container", - "attributes": { - "number": "CRLUYYYYYY", - "seal_number": null, - "created_at": "2020-09-13 14:46:36 UTC", - "equipment_type": "reefer", - "equipment_length": 40, - "equipment_height": "high_cube", - ... - }, - "relationships": { - .... - } - }, - { - "id": "129b695c-c52f-48a0-9949-e2821813690e", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_loaded", - "created_at": "2020-09-13 14:46:36 UTC", - "voyage_number": "032A", - "timestamp": "2020-08-07 06:57:00 UTC", - "location_locode": "PLGDY", - "timezone": "Europe/Warsaw" - }, - ... - } - ] -} -``` - -> ### Why so much JSON? (A note on JSON API) ->The Terminal49 API is JSON API compliant, which means that there are nifty libraries which can translate JSON into a fully fledged object model that can be used with an ORM. This is very powerful, but it also requires a larger, more structured payload to power the framework. The tradeoff, therefore, is that it's less convenient if you're parsing the JSON directly. Ultimately we strongly recommend you set yourself up with a good library to use JSON API to its fullest extent. But for the purposes of understanding the API's fundamentals and getting your feet wet, we'll work with the data directly. - -### What type of webhook event is this? - -This is the first question you need to answer so your code can handle the webhook. - -The type of update can be found in ["data"]["attributes"]. - -The most common Webhook notifications are status updates on tracking requests, like **tracking_request.succeeded** and updates on ETAs, shipment milestone, and terminal availability. - -You can find what type of event you have received by looking at the "attributes", "event". - -```jsx -"data" : { - ... - "attributes": { - "id": "87d4f5e3-df7b-4725-85a3-b80acc572e5d", - "event": "tracking_request.succeeded", - "delivery_status": "pending", - "created_at": "2020-09-13 14:46:37 UTC" - }, -} -``` - -### Inclusions: Tracking Requests & Shipment Data - -When a tracking request has succeeded, the webhook event **includes** information about the shipment, the containers in the shipment, and the milestones for that container, so your app can present this information to your end users without making further queries to the API. - -In the payload below (again, truncated by ellipses for clarity) you'll see a list of JSON objects in the "included" section. Each object has a **type** and **attributes**. The type tells you what the object is. The attributes tell you the data that the object carries. - -Some objects have **relationships**. These are simply links to another object. The most essential objects in relationships are often included, but objects that don't change very often, for example an object that describes a teminal, are not included - once you query these, you should consider caching them locally. - -```jsx - "included":[ - { - "id": "90873f19-f9e8-462d-b129-37e3d3b64c82", - "type": "tracking_request", - "attributes" : { - ... - } - }, - { - "id": "66db1d2a-eaa1-4f22-ba8d-0c41b051c411", - "type": "shipment", - "attributes": { - "created_at": "2020-09-13 14:46:36 UTC", - "bill_of_lading_number": "MEDUNXXXXXX", - "ref_numbers":[ - null - ], - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "port_of_lading_locode": "PLGDY", - "port_of_lading_name": "Gdynia", - .... - }, - "relationships": { - ... - }, - "links": { - "self": "/v2/shipments/66db1d2a-eaa1-4f22-ba8d-0c41b051c411" - } - }, - { - "id": "4d556105-015e-4c75-94a9-59cb8c272148", - "type": "container", - "attributes": { - "number": "CRLUYYYYYY", - "seal_number": null, - "created_at": "2020-09-13 14:46:36 UTC", - "equipment_type": "reefer", - "equipment_length": 40, - "equipment_height": "high_cube", - ... - }, - "relationships": { - .... - } - }, - { - "id": "129b695c-c52f-48a0-9949-e2821813690e", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_loaded", - "created_at": "2020-09-13 14:46:36 UTC", - "voyage_number": "032A", - "timestamp": "2020-08-07 06:57:00 UTC", - "location_locode": "PLGDY", - "timezone": "Europe/Warsaw" - }, - ... - } - ] -``` - -## Code Examples - -### Registering a Webhook - -```jsx -function registerWebhook(){ - // Make a POST request with a JSON payload. - options = { - "method" : "POST" - "headers" : { - "content-type": "application/vnd.api+json", - "authorization" : "Token YOUR_API_KEY" - }, - "payload" : { - "data": { - "type": "webhook", - "attributes": { - "url": "http://yourwebhookurl.com/webhook", - "active": true, - "events": ["tracking_request.succeeded"] - } - } - } - }; - - options.payload = JSON.stringify(data) - var response = UrlFetchApp.fetch('https://api.terminal49.com/v2/webhooks', options); -} -``` - -### Receiving a Post Webhook - -Here's an example of some Javascript code that receives a Post request and parses out some of the desired data. - -``` -function receiveWebhook(postReq) { - try { - var json = postReq.postData.contents; - var webhook_raw = JSON.parse(json); - var webhook_data = webhook_raw["data"] - var notif_string = ""; - if (webhook_data["type"] == "webhook_notification"){ - if (webhook_data["attributes"]["event"] == "shipment.estimated.arrival"){ - /* the webhook "event" attribute tell us what event we are being notified - * about. You will want to write a code path for each event type. */ - - var webhook_included = webhook_raw["included"]; - // from the list of included objects, extract the information about the ETA update. This should be singleton. - var etas = webhook_included.filter(isEstimatedEvent); - // from the same list, extract the tracking Request information. This should be singleton. - var trackingReqs = webhook_included.filter(isTrackingRequest); - if(etas.length > 0 && trackingReqs.length > 0){ - // therethis is an ETA updated for a specific tracking request. - notif_string = "Estimated Event Update: " + etas[0]["attributes"]["event"] + " New Time: " + etas[0]["attributes"]["estimated_timestamp"]; - notif_string += " for Tracking Request: " + trackingReqs[0]["attributes"]["request_number"] + " Status: " + trackingReqs[0]["attributes"]["status"]; - } else { - // this is a webhook type we haven't written handling code for. - notif_string = "Error. Webhook Returned Unexpected Data."; - } - if (webhook_data["attributes"]["event"] == "shipment.estimated.arrival"){ - - } - } - return HtmlService.createHtmlOutput(notf_string); - } catch (error){ - return HtmlService.createHtmlOutput("Webhook failed: " + error); - } - -} - -// JS helper functions to filter events of certain types. -function isEstimatedEvent(item){ - return item["type"] == "estimated_event"; -} - -function isTrackingRequest(item){ - return item["type"] == "tracking_request"; -} -``` - -## Try It Out & See More Sample Code - -Update your API key below, and register a simple Webhook. - -View the "Code Generation" button to see sample code. - -```json http -{ - "method": "post", - "url": "https://api.terminal49.com/v2/webhooks", - "headers": { - "Content-Type": "application/vnd.api+json", - "Authorization": "Token YOUR_API_KEY" - }, - "body": "{\r\n \"data\": {\r\n \"type\": \"webhook\",\r\n \"attributes\": {\r\n \"url\": \"https:\/\/webhook.site\/\",\r\n \"active\": true,\r\n \"events\": [\r\n \"tracking_request.succeeded\"\r\n ]\r\n }\r\n }\r\n}" -} -``` \ No newline at end of file diff --git a/docs/api-docs/getting-started/start-here.mdx b/docs/api-docs/getting-started/start-here.mdx deleted file mode 100644 index edd529e9..00000000 --- a/docs/api-docs/getting-started/start-here.mdx +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: 1. Start Here ---- -So you want to start tracking your ocean shipments and containers and you have a few BL numbers. Follow the guide. - -Our API responses use [JSONAPI](https://jsonapi.org/) schema. There are [client libraries](https://jsonapi.org/implementations/#client-libraries) available in almost every language. Our API should work with these libs out of the box. - -Our APIs can be used with any HTTP client; choose your favorite! We love Postman, it's a friendly graphical interface to a powerful cross-platform HTTP client. Best of all it has support for the OpenAPI specs that we publish with all our APIs. We have created a collection of requests for you to easily test the API endpoints with your API Key. Link to the collection below. - - -**Run in Postman** - - -*** -## Get an API Key -Sign in to your Terminal49 account and go to your [developer portal](https://app.terminal49.com/developers/api-keys) page to get your API key. - -### Authentication -When passing your API key it should be prefixed with `Token`. For example, if your API Key is 'ABC123' then your Authorization header would look like: - -``` -"Authorization": "Token ABC123" -``` \ No newline at end of file diff --git a/docs/api-docs/getting-started/tracking-shipments-and-containers.mdx b/docs/api-docs/getting-started/tracking-shipments-and-containers.mdx deleted file mode 100644 index ab412424..00000000 --- a/docs/api-docs/getting-started/tracking-shipments-and-containers.mdx +++ /dev/null @@ -1,206 +0,0 @@ ---- -title: 2. Tracking Shipments & Containers -description: Submitting a tracking request is how you tell Terminal49 to track a shipment for you. -og:title: Tracking Shipments and Containers | Terminal49 API -og:description: Track shipments and containers with ease via Terminal49's API. Optimize your shipping processes with seamless tracking. ---- - -## What is a Tracking Request? -Your tracking request includes two pieces of data: - - - Your Bill of Lading, Booking number, or container number from the carrier. - - The SCAC code for that carrier. - -You can see a complete list of supported SCACs in row 2 of the Carrier Data Matrix. - -## What sort of numbers can I track? - -**Supported numbers** - - 1. Master Bill of Lading from the carrier (recommended) - 2. Booking number from the carrier - 3. Container number - -* Container number tracking support across ocean carriers is sometimes more limited. Please refer to the Carrier Data Matrix to see which SCACs are compatible with Container number tracking. - -**Unsupported numbers** - - - House Bill of Lading numbers (HBOL) - - Customs entry numbers - - Seal numbers - - Internally generated numbers, for example PO numbers or customer reference numbers. - -## How do I use Tracking Requests? -Terminal49 is an event-based API, which means that the API can be used asynchronously. In general the data flow is: - - 1. You send a tracking request to the API with your Bill of Lading number and SCAC. - 2. The API will respond that it has successfully received your Tracking Request and return the Shipment's data that is available at that time. - 3. After you have submitted a tracking request, the shipment and all of the shipments containers are tracked automatically by Terminal49. - 4. You will be updated when anything changes or more data becomes available. Terminal49 sends updates relating to your shipment via posts to the webhook you have registered. Generally speaking, updates occur when containers reach milestones. ETA updates can happen at any time. As the ship approaches port, you will begin to receive Terminal Availability data, Last Free day, and so forth. - 5. At any time, you can directly request a list of shipments and containers from Terminal49, and the API will return current statuses and information. This is covered in a different guide. - -## How do you send me the data relating to the tracking request? -You have two options. First, you can poll for updates. This is the way we'll show you first. - -You can poll the `GET /tracking_request/{id}` endpoint to see the status of your request. You just need to track the ID of your tracking request, which is returned to you by the API. - -Second option is that you can register a webhook and the API will post updates when they happen. This is more efficient and therefore preferred. But it also requires some work to set up. - -A Webhook is another name for a web-based callback URL, or a HTTP Push API. Webhooks provide a method for an API to post a notification to your service. Specifically, a webhook is simply a URL that can receive HTTP Post Requests from the Terminal49 API. - -When we successfully lookup the Bill of Lading with the Carrier's SCAC, we will create a shipment, and send the event `tracking_request.succeeded` to your webhook endpoint with the associated record. - -If we encounter a problem we'll send the event `tracking_request.failed`. - -![](/images/create-shipment-flow.png) - - -## Authentication -The API uses Bearer Token style authentication. This means you send your API Key as your token in every request. - -To get your API token to Terminal49 and go to your [account API settings](https://app.terminal49.com/settings/api) - -The token should be sent with each API request in the Authentication header: - -Support [dev@terminal49.com](dev@terminal49.com) - -``` -Authorization: Token YOUR_API_KEY -``` - -## How to Create a Tracking Request - -Here is javascript code that demonstates sending a tracking request -```json -fetch("https://api.terminal49.com/v2/tracking_requests", { - "method": "POST", - "headers": { - "content-type": "application/vnd.api+json", - "authorization": "Token YOUR_API_KEY" - }, - "body": { - "data": { - "attributes": { - "request_type": "bill_of_lading", - "request_number": "", - "scac": "" - }, - "type": "tracking_request" - } - } -}) -.then(response => { - console.log(response); -}) -.catch(err => { - console.error(err); -}); -``` -## Anatomy of a Tracking Request Response - -Here's what you'll see in a Response to a tracking request. - -```json -{ - "data": { - "id": "478cd7c4-a603-4bdf-84d5-3341c37c43a3", - "type": "tracking_request", - "attributes": { - "request_number": "xxxxxx", - "request_type": "bill_of_lading", - "scac": "MAEU", - "ref_numbers": [], - "created_at": "2020-09-17T16:13:30Z", - "updated_at": "2020-09-17T17:13:30Z", - "status": "pending", - "failed_reason": null, - "is_retrying": false, - "retry_count": null - }, - "relationships": { - "tracked_object": { - "data": null - } - }, - "links": { - "self": "/v2/tracking_requests/478cd7c4-a603-4bdf-84d5-3341c37c43a3" - } - } -} -``` -Note that if you try to track the same shipment, you will receive an error like this: -```json -{ - "errors": [ - { - "status": "422", - "source": { - "pointer": "/data/attributes/request_number" - }, - "title": "Unprocessable Entity", - "detail": "Request number 'xxxxxxx' with scac 'MAEU' already exists in a tracking_request with a pending or created status", - "code": "duplicate" - } - ] -} -``` - -**Why so much JSON? (A note on JSON API)** - -The Terminal49 API is JSON API compliant, which means that there are nifty libraries which can translate JSON into a fully fledged object model that can be used with an ORM. This is very powerful, but it also requires a larger, more structured payload to power the framework. The tradeoff, therefore, is that it's less convenient if you're parsing the JSON directly. Ultimately we strongly recommend you set yourself up with a good library to use JSON API to its fullest extent. But for the purposes of understanding the API's fundamentals and getting your feet wet, we'll work with the data directly. - - -## Try It: Make a Tracking Request -Try it using the request maker below! - - 1. Enter your API token in the autorization header value. - 2. Enter a value for the `request_number` and `scac`. The request number has to be a shipping line booking or master bill of lading number. The SCAC has to be a shipping line scac (see data sources to get a list of valid SCACs) - -Note that you can also access sample code in multiple languages by clicking the "Code Generation" below. - -**Tracking Request Troubleshooting** - -The most common issue people encounter is that they are entering the wrong number. - -Please check that you are entering the Bill of Lading number, booking number, or container number and not internal reference at your company or by your frieght forwarder. You can the number you are supplying by going to a carrier's website and using their tools to track your shipment using the request number. If this works, and if the SCAC is supported by T49, you should able to track it with us. - -It is entirely possible that's neither us nor you but the shipping line is giving us a headache. Temporary network problems, not populated manifest and other things happen! You can read on how are we handling them in the [Tracking Request Retrying](/api-docs/useful-info/tracking-request-retrying) section. - - - -You can always email us at support@terminal49.com if you have persistent issues. - - -```json -{ - "method": "post", - "url": "https://api.terminal49.com/v2/tracking_requests", - "headers": { - "Content-Type": "application/vnd.api+json", - "Authorization": "Token YOUR_API_KEY" - }, - "body": "{\r\n \"data\": {\r\n \"attributes\": {\r\n \"request_type\": \"bill_of_lading\",\r\n \"request_number\": \"\",\r\n \"scac\": \"\"\r\n },\r\n \"type\": \"tracking_request\"\r\n }\r\n}" -} -``` - -## Try It: List Your Active Tracking Requests -We have not yet set up a webook to receive status updates from the Terminal49 API, so we will need to manually poll to check if the Tracking Request has succeeded or failed. - -**Try it below. Click "Headers" and replace `` with your API key.** - -```json -{ - "method": "get", - "url": "https://api.terminal49.com/v2/tracking_requests", - "headers": { - "Content-Type": "application/vnd.api+json", - "Authorization": "Token YOUR_API_KEY" - } -} -``` -## Next Up: Get your Shipments -Now that you've made a tracking request, let's see how you can list your shipments and retrieve the relevant data. - - -Go to this [page](https://help.terminal49.com/en/articles/8074102-how-to-initiate-shipment-tracking-on-terminal49) to see different ways of initiating shipment tracking on Terminal49. - \ No newline at end of file diff --git a/docs/api-docs/in-depth-guides/adding-customer.mdx b/docs/api-docs/in-depth-guides/adding-customer.mdx deleted file mode 100644 index 3c4242cb..00000000 --- a/docs/api-docs/in-depth-guides/adding-customer.mdx +++ /dev/null @@ -1,170 +0,0 @@ ---- -title: How to add a Customer to a Tracking Request? ---- - -## Why you would want to add a party to a tracking request? - -Adding a party to a tracking request allows you to associate customer information with the tracking request. The customer added to the tracking request will be assigned to the shipment when it is created, just like reference numbers and tags. This can help in organizing and managing your shipments more effectively. - -## How to get the party ID? - -You can either find an existing party or create a new one. - -- To find an existing party, jump to [Listing all parties](#listing-all-parties) section. -- To create a new party, jump to [Adding party for a customer](#adding-party-for-a-customer) section. - -## Listing all parties - -You can list all parties associated with your account through the [API](/api-docs/api-reference/parties/list-parties). - -Endpoint: **GET** - [https://api.terminal49.com/v2/parties](/api-docs/api-reference/parties/list-parties) - -```json Response -{ - "data": [ - { - "id": "PARTY_ID_1", - "type": "party", - "attributes": { - "company_name": "COMPANY NAME 1", - } - }, - { - "id": "PARTY_ID_2", - "type": "party", - "attributes": { - "company_name": "COMPANY NAME 2", - } - } - ], - "links": { - "last": "", - "next": "", - "prev": "", - "first": "", - "self": "" - }, - "meta": { - "size": 2, - "total": 2 - } -} -``` - -After you get all the parties you would filter the parties by `company_name` to find the correct ID, either by looking through the list manually or using code to automate the process. - -## How to add party to tracking request if you have the party ID? - -To add a customer to a tracking request, you need to add the party to the tracking request as a customer relationship while being created. **Note** that a party cannot be added to a tracking request that has already been created. - -Endpoint: **POST** - [https://api.terminal49.com/v2/tracking_requests](/api-docs/api-reference/tracking-requests/create-a-tracking-request) - -```json Request -{ - "data": { - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "MEDUFR030802", - "ref_numbers": [ - "PO12345", - "HBL12345", - "CUSREF1234" - ], - "shipment_tags": [ - "camembert" - ], - "scac": "MSCU" - }, - "relationships": { - "customer": { - "data": { - "id": "PARTY_ID", - "type": "party" - } - } - } - } -} -``` - -After you send a **POST** request to create a tracking request, you will receive a response with the Tracking Request ID and customer relationship. You can use this tracking request ID to track the shipment. - -```json Response -{ - "data": { - "id": "TRACKING_REQUEST_ID", - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "MEDUFR030802", - "ref_numbers": [ - "PO12345", - "HBL12345", - "CUSREF1234" - ], - "shipment_tags": [ - "camembert" - ], - "scac": "MSCU" - }, - "relationships": { - "tracked_object": { - "data": null - }, - "customer": { - "data": { - "id": "PARTY_ID", - "type": "party" - } - } - }, - "links": { - "self": "/v2/tracking_requests/TRACKING_REQUEST_ID" - } - } -} -``` - -## Adding party for a customer - -For adding a customer to a tracking request, you need to create a party first. You can create a party through the [API](/api-docs/api-reference/parties/create-a-party). - -Endpoint: **POST** - [https://api.terminal49.com/v2/parties](/api-docs/api-reference/parties/create-a-party) - -```json Request -{ - "data": { - "type": "party", - "attributes": { - "company_name": "COMPANY NAME" - } - } -} -``` - -After you send a **POST** request to create a party, you will receive a response with the Party ID. You can use this Party ID to add the customer to a tracking request. - -```json Response -{ - "data": { - "id": "PARTY_ID", - "type": "party", - "attributes": { - "company_name": "COMPANY NAME" - } - } -} -``` - -## Editing a party - -You can update existing parties through the [API](/api-docs/api-reference/parties/edit-a-party). - -Endpoint: **PATCH** - [https://api.terminal49.com/v2/parties/PARTY_ID](/api-docs/api-reference/parties/edit-a-party) - -## Reading a party - -You can retrieve the details of an existing party through the [API](/api-docs/api-reference/parties/get-a-party). - -Endpoint: **GET** - [https://api.terminal49.com/v2/parties/PARTY_ID](/api-docs/api-reference/parties/get-a-party) \ No newline at end of file diff --git a/docs/api-docs/in-depth-guides/event-timestamps.mdx b/docs/api-docs/in-depth-guides/event-timestamps.mdx deleted file mode 100644 index 272d1ffb..00000000 --- a/docs/api-docs/in-depth-guides/event-timestamps.mdx +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: Event Timestamps -og:title: Event Timestamps Guide | Terminal49 API Documentation -og:description: Learn how event timestamps are captured and processed through Terminal49's API for improved shipment visibility. ---- -Through the typical container lifecycle events occur across multiple timezones. Wheverever you see a timestamp for some kind of transporation event, there should be a corresponding [IANA tz](https://www.iana.org/time-zones). - -Event timestamps are stored and returned in UTC. If you wish to present them in the local time you need to convert that UTC timestamp using the corresponding timezone. - -### Example - -If you receive a container model with the attributes -``` - 'pod_arrived_at': '2022-12-22T07:00:00Z', - 'pod_timezone': 'America/Los_Angeles', -``` -then the local time of the `pod_arrived_at` timestamp would be `2022-12-21T23:00:00 PST -08:00` - - -## When the corresponding timezone is null -When there is event that occurs where Terminal49 cannot determine the location (and therefore the timezone) of the event the system is unable to store the event in true UTC. - -In this scenario we take timestamp as given from the source and parse it in UTC. - -### Example -``` - 'pod_arrived_at': '2022-12-22T07:00:00Z', - 'pod_timezone': null, -``` - -then the local time of the `pod_arrived_at` timestamp would be `2022-12-22T07:00:00` and the timezone is unknown. (Assuming the source was returning localized timestamps) - - - -## System Timestamps -Timestamps representing changes within the Terminal49 system (e.g. `created_at`, `updated_at`, `terminal_checked_at`) are stored and represented in UTC and do not have a TimeZone. \ No newline at end of file diff --git a/docs/api-docs/in-depth-guides/including-resources.mdx b/docs/api-docs/in-depth-guides/including-resources.mdx deleted file mode 100644 index 20eaa46f..00000000 --- a/docs/api-docs/in-depth-guides/including-resources.mdx +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: Including Resources ---- -Throughout the documentation you will notice that many of the endpoints include a `relationships` object inside of the `data` attribute. - -For example, if you are [requesting a container](/api/4c6091811c4e0-get-a-container) the relationships will include `shipment`, and possibly `pod_terminal` and `transport_events` - -If you want to load the `shipment` and `pod_terminal` without making any additional requests you can add the query parameter `include` and provide a comma delimited list of the related resources: -``` -containers/{id}?include=shipment,pod_terminal -``` -You can even traverse the relationships up or down. For example if you wanted to know the port of lading for the container you could get that with: - -``` -containers/{id}?include=shipment,shipment.port_of_lading -``` - diff --git a/docs/api-docs/in-depth-guides/quickstart.mdx b/docs/api-docs/in-depth-guides/quickstart.mdx deleted file mode 100644 index c377a5db..00000000 --- a/docs/api-docs/in-depth-guides/quickstart.mdx +++ /dev/null @@ -1,125 +0,0 @@ ---- -title: Quick Start Guide -og:title: Quickstart Guide | Terminal49 API Documentation -og:description: Get started quickly with Terminal49's API using this comprehensive guide. Simplify your shipping workflows today. ---- - -## Before You Begin - -You'll need a four things to get started. - -1. **A Bill of Lading (BOL) number.** This is issued by your carrier. BOL numbers are found on your [bill of lading](https://en.wikipedia.org/wiki/Bill_of_lading) document. Ideally, this will be a shipment that is currently on the water or in terminal, but this is not necessary. -2. **The SCAC of the carrier that issued your bill of lading.** The Standard Carrier Alpha Code of your carrier is used to identify carriers in computer systems and in shipping documents. You can learn more about these [here](https://en.wikipedia.org/wiki/Standard_Carrier_Alpha_Code). -3. **A Terminal49 Account.** If you don't have one yet, [sign up here.](https://app.terminal49.com/register) -4. **An API key.** Sign in to your Terminal49 account and go to your [developer portal page](https://app.terminal49.com/developers) to get your API key. - -## Track a Shipment - -You can try this using the embedded request maker below, or using Postman. - -1. Try it below. Click "Headers" and replace YOUR_API_KEY with your API key. In the authorization header value. -2. Enter a value for the `request_number` and `scac`. The request number has to be a shipping line booking or master bill of lading number. The SCAC has to be a shipping line scac (see data sources to get a list of valid SCACs) - -Note that you can also access sample code, include a cURL template, by clicking the "Code Generation" tab in the Request Maker. - -```json http -{ - "method": "post", - "url": "https://api.terminal49.com/v2/tracking_requests", - "headers": { - "Content-Type": "application/vnd.api+json", - "Authorization": "Token YOUR_API_KEY" - }, - "body": "{\r\n \"data\": {\r\n \"attributes\": {\r\n \"request_type\": \"bill_of_lading\",\r\n \"request_number\": \"\",\r\n \"scac\": \"\"\r\n },\r\n \"type\": \"tracking_request\"\r\n }\r\n}" -} -``` - -## Check Your Tracking Request Succeeded - -We have not yet set up a webook to receive status updates from the Terminal49 API, so we will need to manually poll to check if the Tracking Request has succeeded or failed. - - - -> ### Tracking Request Troubleshooting -> The most common issue people encounter is that they are entering the wrong number. -> -> Please check that you are entering the Bill of Lading number, booking number, or container number and not internal reference at your company or by your frieght forwarder. You can the number you are supplying by going to a carrier's website and using their tools to track your shipment using the request number. If this works, and if the SCAC is supported by T49, you should able to track it with us. -> -> You can always email us at support@terminal49.com if you have persistent issues. - -** Try it below. Click "Headers" and replace `` with your API key.** - - -```json http -{ - "method": "get", - "url": "https://api.terminal49.com/v2/tracking_requests", - "headers": { - "Content-Type": "application/vnd.api+json", - "Authorization": "Token YOUR_API_KEY" - } -} -``` - -## List your Tracked Shipments - -If your tracking request was successful, you will now be able to list your tracked shipments. - -**Try it below. Click "Headers" and replace YOUR_API_KEY with your API key.** - -Sometimes it may take a while for the tracking request to show up, but usually no more than a few minutes. - -If you had trouble adding your first shipment, try adding a few more. - -```json http -{ - "method": "get", - "url": "https://api.terminal49.com/v2/shipments", - "headers": { - "Content-Type": "application/vnd.api+json", - "Authorization": "Token YOUR_API_KEY" - } -} -``` - -## List all your Tracked Containers - -You can also list out all of your containers, if you'd like to track at that level. - -Try it after replacing `` with your API key. - -```json http -{ - "method": "get", - "url": "https://api.terminal49.com/v2/containers", - "headers": { - "Content-Type": "application/vnd.api+json", - "Authorization": "Token YOUR_API_KEY" - } -} -``` - - -## Listening for Updates with Webhooks - -The true power of Terminal49's API is that it is asynchronous. You can register a Webhook, which is essentially a callback URL that our systems HTTP Post to when there are updates. - -To try this, you will need to first set up a URL on the open web to receive POST requests. Once you have done this, you'll be able to receive status updates from containers and shipments as they happen, which means you don't need to poll us for updates; we'll notify you. - -** Try it below. Click "Headers" and replace YOUR_API_KEY with your API key.** - -Once this is done, any changes to shipments and containers you're tracking in step 2 will now be sent to your webhook URL as Http POST Requests. - -View the "Code Generation" button to see sample code. - -```json http -{ - "method": "post", - "url": "https://api.terminal49.com/v2/webhooks", - "headers": { - "Content-Type": "application/vnd.api+json", - "Authorization": "Token YOUR_API_KEY" - }, - "body": "{\r\n \"data\": {\r\n \"type\": \"webhook\",\r\n \"attributes\": {\r\n \"url\": \"https:\/\/webhook.site\/\",\r\n \"active\": true,\r\n \"events\": [\r\n \"*\"\r\n ]\r\n }\r\n }\r\n}" -} -``` \ No newline at end of file diff --git a/docs/api-docs/in-depth-guides/rail-integration-guide.mdx b/docs/api-docs/in-depth-guides/rail-integration-guide.mdx deleted file mode 100644 index b7a1e782..00000000 --- a/docs/api-docs/in-depth-guides/rail-integration-guide.mdx +++ /dev/null @@ -1,160 +0,0 @@ ---- -title: 'Integrate Rail Container Tracking Data' -description: 'This guide provides a comprehensive, step-by-step approach for integrating North American Class-1 rail container tracking data into your systems. Whether you are a shipper or a logistics service provider, this guide will help you track all your rail containers via a single API.' -mode: "wide" -og:title: Rail Integration Guide | Terminal49 API Documentation -og:description: Integrate rail data seamlessly with Terminal49's API for accurate and real-time tracking of rail shipments. ---- - - -This is a technical article about rail data within Terminal49's API and DataSync. - -For a broader overview, including the reasons why you'd want rail visibility and how to use it in the Terminal49 dashboard, - [read our announcement post](https://www.terminal49.com/blog/launching-north-american-intermodal-rail-visibility-on-terminal49/). - -## Table of Contents - -- [Supported Rail Carriers](#supported-rail-carriers) -- [Supported Rail Events and Data Attributes](#supported-rail-events-and-data-attributes) - - [Rail-specific Transport Events](#rail-specific-transport-events) - - [Webhook Notifications](#webhook-notifications) - - [Rail Container Attributes](#rail-container-attributes) -- [Integration Methods](#integration-methods) - - [Integration via API](#a-integration-via-api) - - [Integration via DataSync](#b-integration-via-datasync) - -## Supported Rail Carriers - -Terminal49 container tracking platform integrates with all North American Class-1 railroads that handle container shipping, providing comprehensive visibility into your rail container movements. - -- BNSF Railway -- Canadian National Railway (CN) -- Canadian Pacific Railway (CP) -- CSX Transportation -- Norfolk Southern Railway (NS) -- Union Pacific Railroad (UP) - -By integrating with these carriers, Terminal49 ensures that you have direct access to critical tracking data, enabling better decision-making and operational efficiency. - -## Supported Rail Events and Data Attributes - -Terminal49 seamlessly tracks your containers as they go from container ship, to ocean terminal, to rail carrier. - -We provide a [set of Transport Events](#webhook-notifications) that let you track the status of your containers as they move through the rail system. You can be notified by webhook whenever these events occur. - -We also provide a set of attributes [on the container model](/api-docs/api-reference/containers/get-a-container) that let you know the current status of your container at any given time, as well as useful information such as ETA, pickup facility, and availability information. - -### Rail-Specific Transport Events - -There are several core Transport Events that occur on most rail journeys. Some rail carriers do not share all events, but in general these are the key events for a container. - -```mermaid -graph LR -A[Rail Loaded] --> B[Rail Departed] -B --> C[Arrived at Inland Destination] -C --> D[Rail Unloaded] -D --> G[Available for Pickup] -G --> E[Full Out] -E --> F[Empty Return] -``` - -`Available for Pickup`, `Full Out` and `Empty Return` are not specific to rail, but are included here since they are a key part of the rail journey. - -{/* However, not all shipments have a simple journey. You may want to track events such as when a train passes through a town, when the container switches trains or rail carriers at an interchange, and when the status of your container at the terminal changes. - -This is a more complex diagram that shows the various events that can occur in a container's rail journey. - -```mermaid -graph LR - Y[From ocean terminal or carrier] --> A - Z[Rail Interchange Received] --> B - A[Rail Loaded] --> B[Rail Departed] - B --> L[Train Passing] - B --> C[Rail Arrived] - L --> L - L --> C - C --> D[Rail Unloaded] - C --> X[Rail Interchange Delivered] - X --> Z - D --> G[Available for Pickup] - D --> H[Not Available] - H -- Holds and Fees Updated --> H - G --> H - H --> G - G --> E[Full Out] - E --> F[Empty Return] -``` */} - -{/* ```mermaid -graph LR - C[Previous events] --> D[Rail Unloaded] - D --> G[Available for Pickup] - D --> H[Not Available] - G --> H - H --> G - H -- Holds and Fees Updated --> H - G --> E[Full Out] -``` */} - -### Webhook Notifications - -Terminal49 provides webhook notifications to keep you updated on key Transport Events in a container's rail journey. These notifications allow you to integrate near real-time tracking data directly into your applications. - -Here's a list of the rail-specific events which support webhook notifications: - -| Transport Event | Webhook Notification | Description | Example | -|----------------|----------------------|-------------|---------| -| Rail Loaded | `container.transport.rail_loaded` | The container is loaded onto a railcar. | Example | -| Rail Departed | `container.transport.rail_departed` | The container departs on the railcar (not always from port of discharge). | Example | -| Rail Arrived | `container.transport.rail_arrived` | The container arrives at a rail terminal (not always at the destination terminal). | Example | -| Arrived At Inland Destination | `container.transport.arrived_at_inland_destination` | The container arrives at the destination terminal. | Example | -| Rail Unloaded | `container.transport.rail_unloaded` | The container is unloaded from a railcar. | Example | - -There's also a set of events that are triggered when the status of the container at the destination rail terminal changes. For containers without rail, they would have been triggered at the ocean terminal. - -| Transport Event | Webhook Notification | Description | Example | -|----------------|----------------------|-------------|---------| -| Full Out | `container.transport.full_out` | The full container leaves the rail terminal. | Example | -| Empty In | `container.transport.empty_in` | The empty container is returned to the terminal. | Example | - -Finally, we have a webhook notifications for when the destination ETA changes. - -| Transport Event | Webhook Notification | Description | Example | -|----------------|----------------------|-------------|---------| -| Estimated Destination Arrival | `container.transport.estimated.arrived_at_inland_destination` | Estimated time of arrival for the container at the destination rail terminal. | Example | - -Integrate these notifications by subscribing to the webhooks and handling the incoming data to update your systems. - -#### Rail Container Attributes - -The following are new attributes that are specific to rail container tracking. - -- **pod_rail_loaded_at**: Time when the container is loaded onto a railcar at the POD. -- **pod_rail_departed_at**: Time when the container departs from the POD. -- **ind_eta_at**: Estimated Time of Arrival at the inland destination. -- **ind_ata_at**: Actual Time of Arrival at the inland destination. -- **ind_rail_unloaded_at**: Time when the container is unloaded from rail at the inland destination. -- **ind_facility_lfd_on**: Last Free Day for demurrage charges at the inland destination terminal. -- **pod_rail_carrier_scac**: SCAC code of the rail carrier that picks up the container from the POD (this could be different than the rail carrier that delivers to the inland destination). -- **ind_rail_carrier_scac**: SCAC code of the rail carrier that delivers the container to the inland destination. - -{/* TODO: Look at the other container attributes that could be fed via rail but are currently shipping-line-only. Such as :current_issues, :pickup_appointment_at, :availability_known, :available_for_pickup */} - -These attributes can be found on [container objects](/api-docs/api-reference/containers/get-a-container). - -## Integration Methods - -There are two methods to integrate Terminal49's rail tracking data programmatically: via API and DataSync. - -### A. Integration via API - -Terminal49 provides a robust API that allows you to programmatically access rail container tracking data and receive updates via webhooks. You will receive rail events and attributes alongside events and attributes from the ocean terminal and carrier. - -[Here's a step-by-step guide to get started](/api-docs/getting-started/start-here) - - -### B. Integration via DataSync - -Terminal49's DataSync service automatically syncs up-to-date tracking data with your system. The rail data will be in the same tables alongside the ocean terminal and carrier data. - -[Learn more about DataSync](/datasync/overview) diff --git a/docs/api-docs/in-depth-guides/routing.mdx b/docs/api-docs/in-depth-guides/routing.mdx deleted file mode 100644 index 59249042..00000000 --- a/docs/api-docs/in-depth-guides/routing.mdx +++ /dev/null @@ -1,318 +0,0 @@ ---- -title: 'Vessel and Container Route Data' -description: 'This guide explains how to access detailed container routes and vessel positions data (historical and future positions) using Terminal49 APIs.' -mode: "wide" -og:title: Vessel and Route API Guide | Terminal49 API Documentation -og:description: 'Integrate detailed container routes and vessel positions data with Terminal49 APIs for enhanced visibility. Available for paying customers.' ---- - -This is a technical article describing how to use our Routing Data feature, using the map as an example. - - - Routing Data (Container Route and Vessel Positions APIs) is a paid feature. These APIs are subject to additional terms of usage and pricing. If you are interested in using these APIs, please contact sales@terminal49.com. - - -## Table of Contents - -- [Overview of APIs for Mapping](#overview-of-apis-for-mapping) -- [Visualizing Your Container's Journey on a Map](#visualizing-your-container’s-journey-on-a-map) - - [Step 1: Plotting Port Locations](#step-1%3A-plotting-port-locations) - - [Step 2: Drawing Historical Vessel Paths (Actual Route Taken)](#step-2%3A-drawing-historical-vessel-paths-actual-route-taken) - - [Step 3: Drawing Predicted Future Vessel Paths](#step-3%3A-drawing-predicted-future-vessel-paths) - - [Using `GET /v2/vessels/{id}/future_positions_with_coordinates`](#using-get-%2Fv2%2Fvessels%2F%7Bid%7D%2Ffuture-positions-with-coordinates-for-vessels-currently-en-route) - - [Using `GET /v2/vessels/{id}/future_positions`](#using-get-%2Fv2%2Fvessels%2F%7Bid%7D%2Ffuture-positions-with-coordinates-for-vessels-currently-en-route) - - [Combining Data for a Complete Map](#combining-data-for-a-complete-map) -- [Use Cases](#use-cases) -- [Recommendations and Best Practices](#recommendations-and-best-practices) -- [Frequently Asked Questions](#frequently-asked-questions) - -## Overview of APIs for Mapping - -Terminal49 offers a suite of powerful APIs to provide granular details about your container shipments and vessel locations. - -Two key components are: - -* **Container Route API:** Offers detailed information about each part of your container's journey, including port locations (latitude, longitude), vessels involved, and key timestamps. This is foundational for placing port markers on your map. -* **Vessel Positions API:** Provides access to historical and predicted future positions for the vessels. - -## Visualizing Your Container's Journey on a Map - -To create a map visualization of a container's journey (similar to [the embeddable map](/api-docs/in-depth-guides/terminal49-map)), you'll typically combine data from several API endpoints. Here’s a step-by-step approach: - -### Step 1: Plotting Port Locations - -First, retrieve the overall route for the container. This will give you the sequence of ports the container will visit, along with their geographical coordinates. -Use the `GET /v2/containers/{id}/route` endpoint. (See: [Get Container Route API Reference](/api-docs/api-reference/containers/get-container-route)) - -![Port Locations from Container Route](/images/1_route_location_ports.png "Port Locations from Container Route") - - - ```shell Request - curl --request GET \ - --url https://api.terminal49.com/v2/containers/ae1c0b10-3ec2-4292-a95a-483cd2755433/route \ - --header "Authorization: Token YOUR_API_TOKEN" - ``` - - ```json - { - "data": { - "id": "0a14f30f-f63b-4112-9aad-f52e3a1d9bdf", - "type": "route", - "relationships": { - "route_locations": { - "data": [ - { "id": "c781a624-a3bd-429a-85dd-9179c61eb57f", "type": "route_location" }, // POL: Pipavav - { "id": "92258580-8706-478e-a6dc-24e11f972507", "type": "route_location" }, // TS1: Jebel Ali - { "id": "7b6cc511-43f4-4037-9bdd-b0fe5fc0df8f", "type": "route_location" } // TS2: Colombo - // ... more route locations - ] - } - } - }, - "included": [ - { - "id": "4115233f-10b7-4774-ad60-34c100b23760", // Matches a route_location's location data id - "type": "port", - "attributes": { - "name": "Pipavav (Victor) Port", - "code": "INPAV", - "latitude": "20.921010675", - "longitude": "71.509579681" - } - }, - { - "id": "94892d07-ef8f-4f76-a860-97a398c2c177", - "type": "port", - "attributes": { - "name": "Jebel Ali", - "code": "AEJEA", - "latitude": "24.987353081", - "longitude": "55.059917502" - } - }, - // ... other included items like vessels, other ports, and full route_location objects - { - "id": "c781a624-a3bd-429a-85dd-9179c61eb57f", // This is a route_location object - "type": "route_location", - "attributes": { /* ... ATD/ETA times, vessel info ... */ }, - "relationships": { - "location": { // This links to the port object in 'included' - "data": { "id": "4115233f-10b7-4774-ad60-34c100b23760", "type": "port" } - }, - "outbound_vessel": { - "data": { "id": "b868eaf8-9065-4fbe-9e72-f6154246b3c5", "type": "vessel" } - } - } - } - ] - } - ``` - - **How to use:** - 1. Parse the `data.relationships.route_locations.data` array to get the sequence of stops. - 2. For each `route_location` object (found in `included` using its ID from the previous step), find its corresponding physical `location` (port) by looking up the `relationships.location.data.id` in the `included` array (where `type` is `port`). - 3. Use the `latitude` and `longitude` from the port attributes to plot markers on your map (e.g., POL, TS1, TS2 as shown in the image). - 4. Each `route_location` in `included` also contains valuable data like `outbound_atd_at`, `inbound_ata_at`, `outbound_vessel.id`, `inbound_vessel.id` etc., which you'll need for the next steps. - - -### Step 2: Drawing Historical Vessel Paths (Actual Route Taken) - -For segments of the journey that have already been completed, you can draw the vessel's actual path using its historical positions. -Use the `GET /v2/vessels/{id}?show_positions[from_timestamp]={departure_time}&show_positions[to_timestamp]={arrival_time}` endpoint. (See: [Get Vessel Positions API Reference](/api-docs/api-reference/vessels/get-a-vessel-using-the-id) - -![Historical Vessel Path](/images/2_vessel_positions_between_timeframe.png "Historical Vessel Path") - - - ```shell Request (Example for MAERSK BALTIMORE from Pipavav ATD to Jebel Ali ATA) - # Vessel ID: b868eaf8-9065-4fbe-9e72-f6154246b3c5 - # Pipavav (POL) ATD: 2025-05-18T00:48:06Z (from route_location c781a624...) - # Jebel Ali (TS1) ATA: 2025-05-21T09:50:00Z (from route_location 92258580...) - curl --request GET \ - --url 'https://api.terminal49.com/v2/vessels/b868eaf8-9065-4fbe-9e72-f6154246b3c5?show_positions[from_timestamp]=2025-05-18T00:48:06Z&show_positions[to_timestamp]=2025-05-21T09:50:00Z' \ - --header "Authorization: Token YOUR_API_TOKEN" - ``` - - ```json - { - "data": { - "id": "b868eaf8-9065-4fbe-9e72-f6154246b3c5", - "type": "vessel", - "attributes": { - "name": "MAERSK BALTIMORE", - "positions": [ - { "latitude": 20.885, "longitude": 71.498333333, "heading": 195, "timestamp": "2025-05-18T00:48:06Z", "estimated": false }, - // ... many more positions between the two ports - { "latitude": 25.026021667, "longitude": 55.067638333, "heading": 259, "timestamp": "2025-05-21T09:38:07Z", "estimated": false } - ] - } - } - } - ``` - - **How to use:** - 1. From the `/containers/{id}/route` response, for each completed leg (i.e., both ATD from origin and ATA at destination are known): - - Identify the `outbound_vessel.data.id` from the departure `route_location`. - - Use the `outbound_atd_at` (Actual Time of Departure) from the departure `route_location` as the `from_timestamp`. - - Use the `inbound_ata_at` (Actual Time of Arrival) from the arrival `route_location` as the `to_timestamp`. - 2. Call the `/vessels/{vessel_id}?show_positions...` endpoint with these details. - 3. The `attributes.positions` array will contain a series of latitude/longitude coordinates. Plot these coordinates as a connected solid line on your map to represent the vessel's actual historical path for that leg (like the green line from POL to TS1 in the image). - - -### Step 3: Drawing Predicted Future Vessel Paths - -For segments that are currently underway or planned for the future, you can display predicted vessel paths. These are typically shown as dashed lines. - -#### Using `GET /v2/vessels/{id}/future_positions_with_coordinates` (For Vessels Currently En Route) - -This endpoint is used when the vessel **is currently en route** between two ports (e.g., has departed Port A but not yet arrived at Port B). It requires the vessel's current coordinates as input, in addition to the port of departure and the port of arrival for the leg. The output is a predicted path from the vessel's current location to the destination port. -(See: [Get Vessel Future Positions with Coordinates API Reference](/api-docs/api-reference/vessels/get-vessel-future-positions-with-coordinates)) - -![Future Vessel Path with Detailed Coordinates](/images/3_port_route_with_coords.png "Future Vessel Path from Current Position for an En Route Vessel") - - - **How to use:** - 1. **Determine if vessel is en route:** From the `/containers/{id}/route` response, check if the leg has an `outbound_atd_at` from the origin port but no `inbound_ata_at` at the destination port yet. - 2. **Get Current Vessel Coordinates:** - * Identify the `outbound_vessel.data.id` from the departure `route_location`. - * Fetch the vessel's current details using `GET /v2/vessels/{vessel_id}`. The response will contain its latest `latitude`, `longitude`, and `position_timestamp` in the `attributes` section. - ```shell Example: Fetch current vessel data - curl --request GET \ - --url https://api.terminal49.com/v2/vessels/{vessel_id} \ - --header "Authorization: Token YOUR_API_TOKEN" - ``` - - ```json - { - "data": { - "id": "50b58b30-acd6-45d3-a694-19664acb1518", // Example: TB QINGYUAN - "type": "vessel", - "attributes": { - "name": "TB QINGYUAN", - "latitude": 24.419361667, // Current latitude - "longitude": 58.567603333, // Current longitude - "position_timestamp": "2025-05-28T03:55:23Z" - // ... other attributes - } - } - } - ``` - - 3. **Call `future_positions_with_coordinates`:** - * Use the `location.data.id` of the original departure port for this leg (as `previous_port_id` or similar parameter, check API ref). - * Use the `location.data.id` of the final arrival port for this leg (as `port_id` or similar parameter). - * Include the fetched current `latitude` and `longitude` of the vessel in the request. - ```shell Hypothetical Request (e.g., TB QINGYUAN en route from Jebel Ali to Colombo) - # Vessel ID: 50b58b30-acd6-45d3-a694-19664acb1518 (TB QINGYUAN) - # Original Departure Port (Jebel Ali) ID: 94892d07-ef8f-4f76-a860-97a398c2c177 - # Final Arrival Port (Colombo) ID: 818ef299-aed3-49c9-b3f7-7ee205f697f6 - # Current Coords (example): lat=24.4193, lon=58.5676 - curl --request GET \ - --url 'https://api.terminal49.com/v2/vessels/50b58b30-acd6-45d3-a694-19664acb1518/future_positions_with_coordinates?previous_port_id=94892d07-ef8f-4f76-a860-97a398c2c177&port_id=818ef299-aed3-49c9-b3f7-7ee205f697f6¤t_latitude=24.4193¤t_longitude=58.5676' \ - --header "Authorization: Token YOUR_API_TOKEN" - ``` - - ```json - { - "data": { - "id": "50b58b30-acd6-45d3-a694-19664acb1518", - "type": "vessel", - "attributes": { - "name": "TB QINGYUAN", - "positions": [ - // Path starts from near current_latitude, current_longitude - { "latitude": 24.4193, "longitude": 58.5676, "timestamp": "...", "estimated": true }, - // ... several intermediate estimated latitude/longitude points forming a path to Colombo - { "latitude": 6.942742853, "longitude": 79.851136851, "timestamp": "...", "estimated": true } // Colombo - ] - } - } - } - ``` - - 4. **Plot the path:** The `attributes.positions` array will provide a sequence of estimated coordinates starting from (or near) the vessel's current position. Plot these as a connected dashed line on your map (like the dashed line from the vessel's current position between TS1 and TS2, heading towards TS2 in the image). - - -#### Using `GET /v2/vessels/{id}/future_positions` (For Legs Not Yet Started) - -This endpoint is used when the vessel **has not yet departed** from the origin port of a specific leg. It takes the origin port (Port A) and destination port (Port B) of the upcoming leg as input and predicts a path between them. -(See: [Get Vessel Future Positions API Reference](/api-docs/api-reference/vessels/get-vessel-future-positions)) - -![Future Vessel Path Between Ports](/images/4_port_route.png "Future Vessel Path for a Not-Yet-Started Leg") - - - **How to use:** - 1. **Determine if leg has not started:** From the `/containers/{id}/route` response, check if the leg has no `outbound_atd_at` from the origin port (or `outbound_etd_at` is in the future). - 2. **Identify vessel and ports:** - * Get the `outbound_vessel.data.id` that will perform this leg. - * Get the `location.data.id` of the departure port for this leg (as `previous_port_id`). - * Get the `location.data.id` of the arrival port for this leg (as `port_id`). - 3. **Call `future_positions`:** - ```shell Request (Example for CMA CGM COLUMBIA from Algeciras to Tanger Med - assuming not yet departed Algeciras) - # Vessel ID: 17189206-d585-4670-b6dd-0aa50fc30869 (CMA CGM COLUMBIA) - # Departure Port (Algeciras) ID: 0620b5e6-7621-408c-8b44-cf6f0d9a762c - # Arrival Port (Tanger Med) ID: f4ec11ea-8c5a-46f9-a213-9d976af04230 - curl --request GET \ - --url 'https://api.terminal49.com/v2/vessels/17189206-d585-4670-b6dd-0aa50fc30869/future_positions?port_id=f4ec11ea-8c5a-46f9-a213-9d976af04230&previous_port_id=0620b5e6-7621-408c-8b44-cf6f0d9a762c' \ - --header "Authorization: Token YOUR_API_TOKEN" - ``` - - ```json - { - "data": { - "id": "17189206-d585-4670-b6dd-0aa50fc30869", - "type": "vessel", - "attributes": { - "name": "CMA CGM COLUMBIA", - "positions": [ - // Path starts from Algeciras and goes to Tanger Med - { "latitude": 36.142537873, "longitude": -5.438306296, "heading": null, "timestamp": "...", "estimated": true }, // Algeciras - // ... intermediate points - { "latitude": 35.893832072, "longitude": -5.490968974, "heading": null, "timestamp": "...", "estimated": true } // Tanger Med - ] - } - } - } - ``` - - 4. **Plot the path:** The `attributes.positions` array will provide estimated coordinates for the full leg. Plot these as a connected dashed line on your map (like the dashed line from TS3 to TS4 in the image, assuming the vessel is still at TS3). - - -### Combining Data for a Complete Map - -By iterating through the `route_locations` obtained from the initial `/containers/{id}/route` call: -1. Plot all port markers (Step 1). -2. For each leg of the journey: - * If the leg is completed (ATD and ATA are known), use the historical vessel positions API to draw a solid line (Step 2). - * If the leg is in progress or planned for the future (ATD known or ETD known, but ATA is not yet known or is in the future), use one of the future vessel positions APIs to draw a dashed line (Step 3). - -This approach allows you to build a comprehensive map view, dynamically showing completed paths with solid lines and future/in-progress paths with dashed lines, providing a clear visualization of the entire shipment journey. - -## Use Cases - -Integrating Terminal49's Vessel and Container Route APIs enables a variety of advanced capabilities: -- **Track Complete Shipment Journeys Visually:** Monitor shipments across multiple legs on a map, from the port of lading to the port of discharge, including all transshipment points. -- **Identify Transshipment Details Geographically:** Clearly see where transshipments occur and the routes taken between them. -- **Correlate Timestamps with Locations:** Visually connect ETDs, ETAs, ATDs, and ATAs for every leg with their geographical points on the map for precise planning and exception management. -- **Improve Internal Logistics Dashboards:** Offer your operations team a clear visual overview of all ongoing shipments and their current locations. - -## Recommendations and Best Practices - -- **Polling Intervals:** For routing data and vessel positions we recommend refreshing up to once per hour. -- **Efficient Data Handling:** Cache previous vessel positions when possible, as it doesn't change. Focus polling on active vessel movements. -- **Error Handling:** Implement proper error handling for API requests, especially for future predictions which might not always be available for all routes or vessels. - -If you decide to create your own map: - -- **Data Layering:** Consider layering information on your map. Start with basic port markers and paths, then add details like vessel names, ETAs, or status on hover or click. -- **Map Library Integration:** Use a robust mapping library (e.g., Leaflet, Mapbox GL) to handle the rendering of markers, lines, and map interactivity. - -## Frequently Asked Questions - -**Q: How up-to-date is the vessel position data?** -A: Vessel location data is updated every 15 minutes, although that does not guarantee there will be a new position every 15 minutes to factors like whether the vessel is transmitting or within range of a satellite or base station. - -**Q: How accurate are the future predictions?** -Predicted future positions are based on algorithms and current data, and their accuracy can vary based on many factors such as temporary deviations, seasonality, or how frequently the shipping lane is used. - -**Q: What if a vessel deviates from the predicted path?** -A: Predicted paths are estimates. The historical path (once available) will show the actual route taken. Regularly refreshing data for active shipments is key to getting the most accurate information. diff --git a/docs/api-docs/in-depth-guides/terminal49-map.mdx b/docs/api-docs/in-depth-guides/terminal49-map.mdx deleted file mode 100644 index 36bc70b6..00000000 --- a/docs/api-docs/in-depth-guides/terminal49-map.mdx +++ /dev/null @@ -1,221 +0,0 @@ ---- -title: Terminal49 Map Embed Guide -description: The Terminal49 Map allows you to embed real-time visualized container tracking on your website with just a few lines of code. ---- -### Prerequisites - -- A Terminal49 account. -- A Publishable API key, you can get one by reaching out to us at support@terminal49.com. -- Familiarity with our [Shipments API](/api-docs/api-reference/shipments/list-shipments) and [Containers API](/api-docs/api-reference/containers/list-containers). -In the following examples we'll be passing a `containerId` and `shipmentId` variables to the embedded map. -They relate to `id` attributes of the container and shipment objects that are returned by the API. - -### How do I embed the map on my website? - -Once you have the API Key, you can embed the map on your website. - - -1. Copy and paste the code below and insert it on your website. -Once loaded, this will make the map code available through the global `window` object. - -Just before the closing `` tag, add the following link tag to load the map styles. - -```html - - - - Document - - -``` - -Just before the closing `` tag, add the following script tag to load the map code. - -```html - - - - -``` - -2. Define a container where you want the map to be displayed. - -```html -
-``` - -3. After the code is loaded, you can use the `window.TntMap` class to create a map instance. - -```javascript -const map = new window.TntMap("#map", { - authToken: publishableApiKey, -}); -``` - -Notice that the `authToken` option is required. This is where you pass your Publishable API key. - -4. Start the map. -This tells the map to initialize and hook into the element designated during initialization. - -```javascript -await map.start(); -``` - -5. Final step: load a container. You can pass shipment and container ids to the map where it'll fetch the data and display it. - -```javascript -await map.load(shipmentId, containerId); -``` - -6. Putting it all together, here is the javascript code that you need to embed the map on your website. - -```javascript -const map = new window.TntMap("#map", { - authToken: publishableApiKey, -}); - -await map.start(); -await map.load(shipmentId, containerId); -``` - -If you want to use inside the browser you can use the IIFE pattern. - -```javascript - -``` - -Or you can use module attribute to use top-level async/await. - -```javascript - -``` - -![terminal49-map.png](/images/terminal49-map.png) - -Additionally, the map element doesn't have to be an element id but can be a DOM element reference instead. -Consider this example where we use a query selector to select the map element. - -```javascript -const element = document.querySelector("#map"); -const map = new window.TntMap(element, { - authToken: publishableApiKey, -}); -``` - -### Styling the map - -All of the map styles are written as human-readable CSS classes and variables. -You can used those to customize the map to your liking. -The styles are written in [BEM](https://getbem.com/) style as well as they're scoped under a `.tntm` class to avoid style conflicts with your website. - -#### Sizing - -By default the map will take the full width of its container and some height. The map is expandable by clicking on the expand button on the bottom left corner of the map. -You can also override the default styles to customize the map to your liking. - -Let's say you want to tell the map to take 60% of the total viewport size when expanded, we'd do this as follows: - -```css -.tntm .tntm__container.--expanded { - height: 60vh; -} -``` - -![terminal49-map-expanded.png](/images/terminal49-map-expanded.png) - -#### Colors - -We expose a number of CSS variables that you can use to customize the map colors. -All of the variables are bound to the `.tntm` class to avoid style conflicts with your website. -```css -.tntm { - --marker-background-color: var(--athens-gray-500); - --marker-border-color: var(--athens-gray-500); - --marker-text-color: var(--white); - --marker-secondary-background-color: var(--athens-gray-100); - --marker-secondary-text-color: var(--athens-gray-500); -} -``` - -By default their values are set to the Terminal49 brand colors, which we don't recommend changing and only focus on the `--marker` variants instead. -Additionally the variables might require adjusting for different states of the map markers. - -What does that mean? -Let's say we want to display markers 'visited' by a vessel as orange and the others - that we call are in 'on-the-way' state as blue. - -First let's define the default, blue color: - -```css -.tntm [data-journey-state='on-the-way'] { - --marker-background-color: blue; - --marker-border-color: lightblue; - --marker-text-color: var(--white); - --marker-secondary-background-color: lightblue; - --marker-secondary-text-color: black; -} - -.tntm [data-journey-state='visited'] { - --marker-background-color: orange; - --marker-border-color: #FFD580; - --marker-text-color: var(--white); - --marker-secondary-background-color: #FFD580; - --marker-secondary-text-color: black; -} -``` - -Result: - -![terminal49-map-colors.png](/images/terminal49-map-colors.png) - -It's also possible to change the marker colors based on wheter they're hovered over or not. - -This is what we do on the Terminal49 website to style the map markers to our needs: - -```css -[data-journey-state='visited'] { - --marker-background-color: var(--green-600); - --marker-border-color: var(--green-600); - --marker-text-color: var(--white); - --marker-secondary-background-color: var(--green-50); - --marker-secondary-text-color: var(--green-600); -} - -[data-journey-state='on-the-way'] { - --marker-background-color: var(--athens-gray-500); - --marker-border-color: var(--athens-gray-500); - --marker-text-color: var(--white); - --marker-secondary-background-color: var(--athens-gray-100); - --marker-secondary-text-color: var(--athens-gray-500); -} - -[data-hovered][data-journey-state='visited'], -[data-hovered] [data-journey-state='visited'] { - --marker-secondary-background-color: var(--green-200); - --marker-secondary-text-color: var(--green-700); - --marker-border-color: var(--green-700); -} - -[data-hovered][data-journey-state='on-the-way'], -[data-hovered] [data-journey-state='on-the-way'] { - --marker-secondary-background-color: var(--athens-gray-200); - --marker-secondary-text-color: var(--athens-gray-600); - --marker-border-color: var(--athens-gray-600); -} -``` -You might want to copy this code and adjust it to your needs. \ No newline at end of file diff --git a/docs/api-docs/in-depth-guides/terminal49-widget.mdx b/docs/api-docs/in-depth-guides/terminal49-widget.mdx deleted file mode 100644 index 57affe18..00000000 --- a/docs/api-docs/in-depth-guides/terminal49-widget.mdx +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Tracking Widget Embed Guide -description: The Terminal49 Track & Trace Widget allows you to embed real-time container tracking on your website with just a few lines of code. This widget provides a seamless user experience and helps improve customer satisfaction. ---- - - -### How do I embed the widget on my website? - -> First, you neeed a publishable API KEY. You can get this by reaching out to us at support@terminal49.com - - -Once you have the key, you can embed the widget on your website. We suggest creating a dedicated page for tracking, typically at `company-website.com/track`. You can also embed the widget directly on your homepage. If you decide to create a dedicated tracking page, we recommend adding a `h1` tag above the script. Feel free to customize the `h1` contents in the script. - -Copy and pase the code below and insert it on top of the page (under your page navigation if you a horizontal top navigation). Replace `REPLACE_WITH_PUBLISHABLE_KEY` with the `API KEY` you receive from us. We suggest adding a `h1` tag above the script. Feel free to remove change the `h1` contents in the script below. - -To query a bill of lading, container, or reference number, simply replace `REPLACE_WITH_NUMBER_TO_QUERY` with the specific number you want to search for. If `data-number` exists, the query will be performed only once. - -```html - -

Tracking

- -
- -``` - - -## Frequently Asked Questions - -### How does it work? - -With a few lines of code, you can embed an interactive container tracking form. Once the widget is live on your website, your customer can enter a master bill of lading, container number, or reference numbers that a shipment is tagged with. After the number has been entered, the widget will retrieve and display shipment and container details from your Terminal49 account. - -### Do I need Terminal49 account? -Yes, the information that fetched and displayed by the widget is based on the shipments and containers tracked within your Terminal49 account. - -### Can my customer track *any* shipment/container? -No, only the shipments and containers that are tracked in your Terminal49 account. - -### Is there a cost to embed the widget? -Yes, there is a $500/month fee to embed and use the widget. This include unlimited number of visitors and tracking requests. - - -## Terminal49 container tracking widget one-pager - -Here is a one-pager that describes the benefits of the Track & Trace Widget. Feel free to share it with your team or management if you want to demonstrate the benefits of adding track and trace functionality to your website. - -The Track & Trace Widget provides a number of advantages: - -- It offers your customers a convenient way to track their shipments and containers. -- It helps to improve customer satisfaction by providing accurate container status. -- It can reduce customer service costs by providing customers with the information they need without having to contact customer service. -- It can help you differentiate from other service providers. - - -![terminal49-container-tracking-widget.jpg](/images/terminal49-container-tracking-widget.png) \ No newline at end of file diff --git a/docs/api-docs/in-depth-guides/tracking-request-lifecycle.mdx b/docs/api-docs/in-depth-guides/tracking-request-lifecycle.mdx deleted file mode 100644 index b519aaba..00000000 --- a/docs/api-docs/in-depth-guides/tracking-request-lifecycle.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: Tracking Request Lifecycle ---- -When you submit a tracking request your request is added to our queue to being checked at the shipping line. So what happens if the request doesn't go through correctly? - -If we are having difficulty connecting to the shipping line, or if we are unable to parse the response from the shipping line, we will keep retrying up to 14 times. - -This process can take up to approximately 24 hours. You will not receive a `tracking_request.failed` webhook notification until we have exhausted the retries, and the `status` field will not be changed to `failed` until then. - -## Request Number Not Found / Awaiting Manifest - -If the shipping line returns a response that it cannot find the provided number we either immediately fail the tracking request or keep trying depending on whether the `request_type` is a container or not: - - * **Containers** fail straight away after a not found response from the shipping line. - * **Bill of lading** and **booking numbers** do not fail instantly. We change the `status` to `awaiting_manifest` and will keep checking your request daily. You will receive a `tracking_request.awaiting_manifest` webhook notification the first time it happens. If your request number cannot be found after 7 days we will mark the tracking request as failed by changing the `status` field `failed` and sending the `tracking_request.failed` event to your webhook. - * Should you wish to adjust the duration before marking your tracking requests as failed, please contact us through support@terminal49.com. - * **Incorrect request number type** if the request number type (ex. booking number) is incorrect, the tracking request will still fail even though the request number is correct. - - -## Failed Reason - -### Temporary - -The `failed_reason` field can take one of the following temporary values: - - * `unrecognized_response` when we could not parse the response from the shipping line, - * `shipping_line_unreachable` if the shipping line was unreachable, - * `internal_processing_error` when we faced other issue, - * `awaiting_manifest` if the shipping line indidicates a bill of lading number is found, but data is not yet available, or if the requested number could not be found. - -### Permanent - -Temporary reasons can become permanent when the `status` changes to `failed`: - - * `duplicate` when the shipment already existed, - * `expired` when the tracking request was created more than 7 days ago and still not succeded, - * `retries_exhausted` if we tried for 14 times to no avail, - * `not_found` if the shipping line could not find the BL number. - * `invalid_number` if the shipping line rejects the formatting of the number. - * `booking_cancelled` if the shipping line indicates that the booking has been cancelled. - * `data_unavailable` if the number is valid but the shipping line will not provide the data. Examples include shipments that are flagged as private or results that are removed due to data retention policies. - -[Failed Reasons when tracking request through dashboard](https://help.terminal49.com/en/articles/6116676-what-happens-after-i-add-a-shipment-to-terminal49-recently-added-shipments#h_ac9b93504f) - -## Stopped - -When a shipment is no longer being updated then the tracking request `status` is marked as `tracking_stopped`. - -You may subscribe to the event `tracking_request.tracking_stopped` for notifications when this occurs. - -Terminal49 will stop tracking requests for the following reasons: - - * The booking was cancelled. - * The data is no longer available at the shipping line. - * All shipment containers are marked `empty_returned`. - * More than 56 days have passed since the shipment arrived at it's destination. - * There have been no updates from the shipping line for more than 56 days. - - In addition end-users may stop tracking a shipment through the dashboard. - -## Retrieving Status - -If you want to see the status of your tracking request you can make a [GET request](/api-docs/api-reference/tracking-requests/get-a-single-tracking-request) on what the most recent failure reason was (`failed_reason` field). \ No newline at end of file diff --git a/docs/api-docs/in-depth-guides/webhooks.mdx b/docs/api-docs/in-depth-guides/webhooks.mdx deleted file mode 100644 index 9975ae9e..00000000 --- a/docs/api-docs/in-depth-guides/webhooks.mdx +++ /dev/null @@ -1,1444 +0,0 @@ ---- -title: Webhooks ---- - -## Creating Webhooks -You may subscribe to events through webhooks to be alerted when events are triggered. - -Visit https://app.terminal49.com/developers/webhooks and click the 'Create Webhook Endpoint' button to create your webhook through the UI. - -If you prefer to create webhooks programatically then see the [webhooks post endpoint documentation](/api-docs/api-reference/webhooks/create-a-webhook). - - -## Available Webook Events - -Each `WebhookNotification` event represents some change to a model which you may be notified of. - -List of Supported Events: - -Event | Description ----------|---------- - `tracking_request.succeeded` | Shipment created and linked to `TrackingRequest` - `tracking_request.failed` | `TrackingRequest` failed and shipment was not created - `tracking_request.awaiting_manifest` | `TrackingRequest` awaiting a manifest - `tracking_request.tracking_stopped` | Terminal49 is no longer updating this `TrackingRequest`. - `container.transport.empty_out` | Empty out at port of lading - `container.transport.full_in` | Full in at port of lading - `container.transport.vessel_loaded` | Vessel loaded at port of lading - `container.transport.vessel_departed` | Vessel departed at port of lading - `container.transport.transshipment_arrived` | Container arrived at transhipment port - `container.transport.transshipment_discharged` | Container discharged at transhipment port - `container.transport.transshipment_loaded` | Container loaded at transhipment port - `container.transport.transshipment_departed` | Container departed at transhipment port - `container.transport.feeder_arrived` | Container arrived on feeder vessel or barge - `container.transport.feeder_discharged` | Container discharged from feeder vessel or barge - `container.transport.feeder_loaded` | Container loaded on feeder vessel or barge - `container.transport.feeder_departed` | Container departed on feeder vessel or barge - `container.transport.vessel_arrived` | Container arrived on vessel at port of discharge (destination port) - `container.transport.vessel_berthed` | Container on vessel berthed at port of discharge (destination port) - `container.transport.vessel_discharged` | Container discharged at port of discharge - `container.transport.full_out` | Full out at port of discharge - `container.transport.empty_in` | Empty returned at destination - `container.transport.rail_loaded` | Rail loaded - `container.transport.rail_departed` | Rail departed - `container.transport.rail_arrived` | Rail arrived - `container.transport.rail_unloaded` | Rail unloaded - `shipment.estimated.arrival` | ETA change notification (for port of discharge) - `container.created` | Container added to shipment. Helpful for seeing new containers on a booking or BL. - `container.updated` | Container attribute(s) updated (see below example) - `container.pod_terminal_changed` | Port of discharge assignment changed for container - `container.transport.arrived_at_inland_destination` | Container arrived at inland destination - `container.transport.estimated.arrived_at_inland_destination` | ETA change notification (for destination) - `container.pickup_lfd.changed` | Last Free Day (LFD) changed for container - `container.transport.available` | Container is available at destination - - - -## Receiving Webhooks - -When an event is triggered we will attempt to post to the URL you provided with the webhook. - -The payload of every webhook is a `webhook_notification`. Each Webhook notification includes a `reference_object` in it's relationships which is the subject of that notification (e.g. a tracking request, or an updated container). - -Please note that we expect the endpoint to return [HTTP 200 OK](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200), [HTTP 201](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201), [HTTP 202](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202) or [HTTP 204](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204). We aim to deliver all webhook notifications, so any other response, including timeout, will result in a dozen of retries. - -```json json_schema -{ - "type":"object", - "properties":{ - "data":{ - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "webhook_notification" - ] - }, - "attributes": { - "type": "object", - "properties": { - "event": { - "type": "string" - }, - "delivery_status": { - "type": "string", - "default": "pending", - "enum": [ - "pending", - "succeeded", - "failed" - ], - "description": "Whether the notification has been delivered to the webhook endpoint" - }, - "created_at": { - "type": "string" - } - }, - "required": [ - "event", - "delivery_status", - "created_at" - ] - }, - "relationships": { - "type": "object", - "properties": { - "webhook": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "webhook" - ] - } - } - } - } - }, - "reference_object": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "tracking_request", - "estimated_event", - "transport_event", - "container_updated_event" - ] - } - } - } - } - } - }, - "required": [ - "webhook" - ] - } - } - }, - "included":{ - "type":"array", - "items": { - "anyOf": [ - { - "type": "object", - "title": "Webhook", - }, - { - "type": "object", - "title": "Tracking Request", - }, - { - "type": "object", - "title": "Transport Event", - }, - { - "type": "object", - "title": "Estimated Event", - }, - { - "type": "object", - "title": "Container Updated Event", - }, - { - "type": "object", - "title": "Terminal", - }, - { - "type": "object", - "title": "Port", - }, - - ] - } - } - - } -} -``` - -> [How to Troubleshoot Missing Webhook Notifications](https://help.terminal49.com/en/articles/7851422-missing-webhook-notifications) - - -## Security -There are a few ways you can verify the webhooks sent by Terminal49. - -Verify webhook signatures to confirm that received events are sent from Terminal49. Additionally, Terminal49 sends webhook events from a set list of IP addresses. Only trust events coming from these IP addresses. - - - -### Webhook notification origin IP -The full list of IP addresses that webhook notifications may come from is: - -``` -35.222.62.171 -3.230.67.145 -44.217.15.129 -``` - -### Verifying the webhook signature (optional) -When you create or get a webhook the model will include an attribute `secret`. - -Whenever a webhook notification is delivered we create a signature by using the webhook `secret` as the key to generate a HMAC hex digest with SHA-256 on the body. - -This signature is added as the header `X-T49-Webhook-Signature` - -If you would like to verify that the webhook payload has not been tampered with by a 3rd party, then you can perform the same operation on the response body with the webhook secret and confirm that the digests match. - -Below is a basic example of how this might look in a rails application. -```ruby -class WebhooksController < ApplicationController - def receive_tracking_request - secret = ENV.fetch('TRACKING_REQUEST_WEBHOOK_SECRET') - raise 'InvalidSignature' unless valid_signature?(request, secret) - - # continue processing webhook payload... - - end - - private - - def valid_signature?(request, secret) - hmac = OpenSSL::HMAC.hexdigest('SHA256', secret, request.body.read) - request.headers['X-T49-Webhook-Signature'] == hmac - end -end -``` - - - -## Webhook Notification Examples - - -### container.updated - -The container updated event lets you know about changes to container properties at the terminal, or which terminal the container is (or will be) located at. - -The `changeset` attribute on is a hash of all the properties which changed on the container. - -Each changed property is the hash key. The prior value is the first item in the array, and the current value is the second item in the array. - -For example: -``` -"changeset": { - "pickup_lfd": [null, "2020-05-20 00:00:00"] -} -``` -Shows that the pickup last free day has changed from not being set to May 20 2020. - -The properties we show changes for are: -- fees_at_pod_terminal -- holds_at_pod_terminal -- pickup_lfd -- pickup_appointment_at -- available_for_pickup -- pod_terminal - -In every case the attribute `container_updated.timestamp` tells you when we picked up the changes from the terminal. - - -As container availability becomes known or changes at the POD Terminal we will send `container_updated` events with the key `available_for_pickup` in the `changeset`. -```json -{ - "data": { - "id": "fa1a6731-4b34-4b0c-aabc-460892055ba1", - "type": "webhook_notification", - "attributes": { - "id": "fa1a6731-4b34-4b0c-aabc-460892055ba1", - "event": "container.updated", - "delivery_status": "pending", - "created_at": "2023-01-24T00:11:32Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "e8f1976c-0089-4b98-96ae-90aa87fbdfee", - "type": "container_updated_event" - } - }, - "webhook": { - "data": { - "id": "8a5ffa8f-3dc1-48de-a0ea-09fc4f2cd96f", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "adc08630-51d3-4bbc-a859-5157cbbe806c", - "type": "shipment", - "attributes": { - "created_at": "2023-01-24T00:11:32Z", - "ref_numbers": [ - "REF-50FFA3", - "REF-5AC291" - ], - "tags": [ - - ], - "bill_of_lading_number": "TE49DD306F13", - "normalized_number": "TE49DD306F13", - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "shipping_line_short_name": "MSC", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2023-01-11T00:11:32Z", - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2023-01-23T20:11:32Z", - "pod_ata_at": "2023-01-23T23:11:32Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": "2023-01-24T00:11:32Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "588711e2-3f78-4178-ae5e-ccb690e0671d", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "9a25e0aa-52bd-4bb8-8876-cd7616f5fb0f", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "4960e227-93b1-4f85-bf7c-07c9b6f597e0", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": { - "id": "26d8be45-b428-45fa-819b-46c828bf6fac", - "type": "terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "3cd51f0e-eb18-4399-9f90-4c8a22250f63", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/adc08630-51d3-4bbc-a859-5157cbbe806c" - } - }, - { - "id": "9a25e0aa-52bd-4bb8-8876-cd7616f5fb0f", - "type": "port", - "attributes": { - "id": "9a25e0aa-52bd-4bb8-8876-cd7616f5fb0f", - "name": "Port of Oakland", - "code": "USOAK", - "state_abbr": "CA", - "city": "Oakland", - "country_code": "US", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "4960e227-93b1-4f85-bf7c-07c9b6f597e0", - "type": "terminal", - "attributes": { - "id": "4960e227-93b1-4f85-bf7c-07c9b6f597e0", - "nickname": "SSA", - "name": "SSA Terminal", - "firms_code": "Z985" - }, - "relationships": { - "port": { - "data": { - "id": "9a25e0aa-52bd-4bb8-8876-cd7616f5fb0f", - "type": "port" - } - } - } - }, - { - "id": "3cd51f0e-eb18-4399-9f90-4c8a22250f63", - "type": "container", - "attributes": { - "number": "COSU1186800", - "seal_number": "43e29239e5dd5276", - "created_at": "2023-01-24T00:11:32Z", - "ref_numbers": [ - "REF-C86614", - "REF-456CEA" - ], - "pod_arrived_at": "2023-01-23T23:11:32Z", - "pod_discharged_at": "2023-01-24T00:11:32Z", - "final_destination_full_out_at": null, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": 43333, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "holds_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "availability_known": true, - "available_for_pickup": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "adc08630-51d3-4bbc-a859-5157cbbe806c", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "4960e227-93b1-4f85-bf7c-07c9b6f597e0", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - - ] - }, - "raw_events": { - "data": [ - - ] - } - } - }, - { - "id": "e8f1976c-0089-4b98-96ae-90aa87fbdfee", - "type": "container_updated_event", - "attributes": { - "changeset": { - "available_for_pickup": [ - false, - true - ] - }, - "timestamp": "2023-01-24T00:11:32Z", - "data_source": "terminal", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "container": { - "data": { - "id": "3cd51f0e-eb18-4399-9f90-4c8a22250f63", - "type": "container" - } - }, - "terminal": { - "data": { - "id": "4960e227-93b1-4f85-bf7c-07c9b6f597e0", - "type": "terminal" - } - }, - "shipment": { - "data": { - "id": "adc08630-51d3-4bbc-a859-5157cbbe806c", - "type": "shipment" - } - } - } - } - ] -} -``` - -The `pod_terminal` is a relationship of the container. When the pod_terminal changes the id is included. The terminal will be serialized in the included models. - -N.B. the `container_updated_event` also has a relationship to a `terminal` which refers to where the information came from. Currently this is always the POD terminal. In the future this may be the final destination terminal or an off-dock location. -```json -{ - "data": { - "id": "f6c5e340-94bf-4681-a47d-f2e8d6c90e59", - "type": "webhook_notification", - "attributes": { - "id": "f6c5e340-94bf-4681-a47d-f2e8d6c90e59", - "event": "container.updated", - "delivery_status": "pending", - "created_at": "2023-01-24T00:13:06Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "567eccef-53bf-43d5-b3d8-00278d7710df", - "type": "container_updated_event" - } - }, - "webhook": { - "data": { - "id": "2e5f41d1-8a3b-4940-a9bb-ff0481e09c71", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "c74ff2a5-5ede-4fc2-886b-3eeef886ff32", - "type": "shipment", - "attributes": { - "created_at": "2023-01-24T00:13:05Z", - "ref_numbers": [ - "REF-29557A" - ], - "tags": [ - - ], - "bill_of_lading_number": "TE497F86D5B7", - "normalized_number": "TE497F86D5B7", - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "shipping_line_short_name": "MSC", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2023-01-11T00:13:05Z", - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2023-01-23T21:13:05Z", - "pod_ata_at": "2023-01-24T00:13:05Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": "2023-01-24T00:13:05Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "8d0f0cba-9961-4fa5-9bf0-0fb5fb67bdbe", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "9722a830-634e-4f7a-b1b3-793ccaf8cbb2", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "08831e36-766b-4ac8-8235-d8594b55ff6d", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": { - "id": "f2a6a6e2-4bd1-4c66-aa8b-be4cb2ddc9a8", - "type": "terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "adf4673d-f4ba-41a9-82da-55c0ae3b3722", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/c74ff2a5-5ede-4fc2-886b-3eeef886ff32" - } - }, - { - "id": "9722a830-634e-4f7a-b1b3-793ccaf8cbb2", - "type": "port", - "attributes": { - "id": "9722a830-634e-4f7a-b1b3-793ccaf8cbb2", - "name": "Port of Oakland", - "code": "USOAK", - "state_abbr": "CA", - "city": "Oakland", - "country_code": "US", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "08831e36-766b-4ac8-8235-d8594b55ff6d", - "type": "terminal", - "attributes": { - "id": "08831e36-766b-4ac8-8235-d8594b55ff6d", - "nickname": "STO", - "name": "Shippers Transport Express", - "firms_code": "STO" - }, - "relationships": { - "port": { - "data": { - "id": "9722a830-634e-4f7a-b1b3-793ccaf8cbb2", - "type": "port" - } - } - } - }, - { - "id": "adf4673d-f4ba-41a9-82da-55c0ae3b3722", - "type": "container", - "attributes": { - "number": "CGMU1560506", - "seal_number": "a9948b719482648c", - "created_at": "2023-01-24T00:13:06Z", - "ref_numbers": [ - "REF-D2AC6F", - "REF-34E84B" - ], - "pod_arrived_at": "2023-01-24T00:13:05Z", - "pod_discharged_at": "2023-01-24T00:13:05Z", - "final_destination_full_out_at": null, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": 43481, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "holds_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "availability_known": true, - "available_for_pickup": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "c74ff2a5-5ede-4fc2-886b-3eeef886ff32", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "08831e36-766b-4ac8-8235-d8594b55ff6d", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - - ] - }, - "raw_events": { - "data": [ - - ] - } - } - }, - { - "id": "0ef5519f-1b39-4f6c-9961-1bbba0ac1307", - "type": "terminal", - "attributes": { - "id": "0ef5519f-1b39-4f6c-9961-1bbba0ac1307", - "nickname": "SSA", - "name": "SSA Terminal", - "firms_code": "Z985" - }, - "relationships": { - "port": { - "data": { - "id": "9722a830-634e-4f7a-b1b3-793ccaf8cbb2", - "type": "port" - } - } - } - }, - { - "id": "567eccef-53bf-43d5-b3d8-00278d7710df", - "type": "container_updated_event", - "attributes": { - "changeset": { - "pod_terminal": [ - "0ef5519f-1b39-4f6c-9961-1bbba0ac1307", - "08831e36-766b-4ac8-8235-d8594b55ff6d" - ] - }, - "timestamp": "2023-01-24T00:13:06Z", - "data_source": "terminal", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "container": { - "data": { - "id": "adf4673d-f4ba-41a9-82da-55c0ae3b3722", - "type": "container" - } - }, - "terminal": { - "data": { - "id": "0ef5519f-1b39-4f6c-9961-1bbba0ac1307", - "type": "terminal" - } - }, - "shipment": { - "data": { - "id": "c74ff2a5-5ede-4fc2-886b-3eeef886ff32", - "type": "shipment" - } - } - } - } - ] -} -``` - - -### tracking_request.succeeded - -```json -{ - "data": { - "id": "a76187fc-5749-43f9-9053-cfaad9790a31", - "type": "webhook_notification", - "attributes": { - "id": "a76187fc-5749-43f9-9053-cfaad9790a31", - "event": "tracking_request.succeeded", - "delivery_status": "pending", - "created_at": "2020-09-11T21:25:34Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "bdeca506-9741-4ab1-a0a7-cfd1d908e923", - "type": "tracking_request" - } - }, - "webhook": { - "data": { - "id": "914b21ce-dd7d-4c49-8503-65aba488e9a9", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [] - } - } - }, - "included": [ - { - "id": "bdeca506-9741-4ab1-a0a7-cfd1d908e923", - "type": "tracking_request", - "attributes": { - "request_number": "TE497ED1063E", - "request_type": "bill_of_lading", - "scac": "MSCU", - "ref_numbers": [], - "created_at": "2020-09-11T21:25:34Z", - "updated_at": "2020-09-11T22:25:34Z", - "status": "created", - "failed_reason": null, - "is_retrying": false, - "retry_count": null - }, - "relationships": { - "tracked_object": { - "data": { - "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", - "type": "shipment" - } - } - }, - "links": { - "self": "/v2/tracking_requests/bdeca506-9741-4ab1-a0a7-cfd1d908e923" - } - }, - { - "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", - "type": "shipment", - "attributes": { - "created_at": "2020-09-11T21:25:33Z", - "bill_of_lading_number": "TE497ED1063E", - "ref_numbers": [], - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2020-08-29T21:25:33Z", - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2020-09-18T21:25:33Z", - "pod_ata_at": null, - "pod_timezone": "America/Los_Angeles" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "4384d6a5-5ccc-43b7-8d19-4a9525e74c08", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "2a765fdd-c479-4345-b71d-c4ef839952e2", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "17891bc8-52da-40bf-8ff0-0247ec05faf1", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "containers": { - "data": [ - { - "id": "b2fc728c-e2f5-4a99-8899-eb7b34ef22d7", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/b5b10c0a-8d18-46da-b4c2-4e5fa790e7da" - } - }, - { - "id": "b2fc728c-e2f5-4a99-8899-eb7b34ef22d7", - "type": "container", - "attributes": { - "number": "ARDU1824900", - "seal_number": "139F1451", - "created_at": "2020-09-11T21:25:34Z", - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": 53507, - "fees_at_pod_terminal": [], - "holds_at_pod_terminal": [], - "pickup_lfd": null, - "pickup_appointment_at": null, - "availability_known": true, - "available_for_pickup": false, - "pod_arrived_at": null, - "pod_discharged_at": null, - "location_at_pod_terminal": null, - "final_destination_full_out_at": null, - "pod_full_out_at": null, - "empty_terminated_at": null - }, - "relationships": { - "shipment": { - "data": { - "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "17891bc8-52da-40bf-8ff0-0247ec05faf1", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "56078596-5293-4c84-9245-cca00a787265", - "type": "transport_event" - } - ] - } - } - }, - { - "id": "56078596-5293-4c84-9245-cca00a787265", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_departed", - "created_at": "2020-09-11T21:25:34Z", - "voyage_number": null, - "timestamp": "2020-08-29T21:25:33Z", - "location_locode": "MXZLO", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "b2fc728c-e2f5-4a99-8899-eb7b34ef22d7", - "type": "container" - } - }, - "vessel": { - "data": null - }, - "location": { - "data": { - "id": "2a765fdd-c479-4345-b71d-c4ef839952e2", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - } - ] -} -``` - -### shipment.estimated.arrival - -```json -{ - "data": { - "id": "b03bcf3c-252d-41f8-b86f-939b404e304b", - "type": "webhook_notification", - "attributes": { - "id": "b03bcf3c-252d-41f8-b86f-939b404e304b", - "event": "shipment.estimated.arrival", - "delivery_status": "pending", - "created_at": "2022-01-13T19:56:58Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "14b5047f-e3e7-4df7-a570-2d3878e6d863", - "type": "estimated_event" - } - }, - "webhook": { - "data": { - "id": "d60a23a4-f40d-44d2-8b6a-2e55a527e6a2", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "14b5047f-e3e7-4df7-a570-2d3878e6d863", - "type": "estimated_event", - "attributes": { - "created_at": "2022-01-13T19:56:58Z", - "estimated_timestamp": "2022-01-16T19:56:58Z", - "voyage_number": "098N", - "event": "shipment.estimated.arrival", - "location_locode": "USOAK", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "8e4a1f1e-aa13-4cad-9df0-aec6c791a5f8", - "type": "shipment" - } - }, - "port": { - "data": { - "id": "3ee88ea1-3b8b-4b96-80fb-6aa23ba7065e", - "type": "port" - } - }, - "vessel": { - "data": { - "id": "b1550abc-4e73-4271-a0f4-8ac031f242cd", - "type": "vessel" - } - } - } - }, - { - "id": "3ee88ea1-3b8b-4b96-80fb-6aa23ba7065e", - "type": "port", - "attributes": { - "id": "3ee88ea1-3b8b-4b96-80fb-6aa23ba7065e", - "name": "Port of Oakland", - "code": "USOAK", - "state_abbr": "CA", - "city": "Oakland", - "country_code": "US", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "8e4a1f1e-aa13-4cad-9df0-aec6c791a5f8", - "type": "shipment", - "attributes": { - "created_at": "2022-01-13T19:56:58Z", - "ref_numbers": [ - "REF-3AA505", - "REF-910757", - "REF-2A8357" - ], - "tags": [ - - ], - "bill_of_lading_number": "TE49C31E16E2", - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "shipping_line_short_name": "MSC", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2021-12-31T19:56:58Z", - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2022-01-16T19:56:58Z", - "pod_ata_at": null, - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": "2022-01-13T19:56:58Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "78ad2915-700b-4919-8ede-a3b6c2137436", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "3ee88ea1-3b8b-4b96-80fb-6aa23ba7065e", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "3bd88777-48ea-4880-9cb9-961dd4d26a00", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": { - "id": "1d016b3d-96d5-4867-8f99-77233d1cc57d", - "type": "terminal" - } - }, - "containers": { - "data": [ - - ] - } - }, - "links": { - "self": "/v2/shipments/8e4a1f1e-aa13-4cad-9df0-aec6c791a5f8" - } - } - ] -} -``` - -### container.transport.vessel_arrived - -```json -{ - "data": { - "id": "72f8b0b5-28f5-4a12-8274-71d4d23c9ab7", - "type": "webhook_notification", - "attributes": { - "id": "72f8b0b5-28f5-4a12-8274-71d4d23c9ab7", - "event": "container.transport.vessel_arrived", - "delivery_status": "pending", - "created_at": "2023-01-24T00:14:28Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "c1443820-304a-444b-bf42-c3d885dc8daa", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "655236f8-7936-4611-b580-341d3e1103f5", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "290a696b-5fba-45aa-a08c-0e15ae89e9c0", - "type": "shipment", - "attributes": { - "created_at": "2023-01-24T00:14:28Z", - "ref_numbers": [ - "REF-134938", - "REF-BE2704", - "REF-712D47" - ], - "tags": [ - - ], - "bill_of_lading_number": "TE49735F4B1D", - "normalized_number": "TE49735F4B1D", - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "shipping_line_short_name": "MSC", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2023-01-11T00:14:28Z", - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2023-01-31T00:14:28Z", - "pod_ata_at": "2023-01-31T01:14:28Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": "2023-01-24T00:14:28Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "036084b7-f2cc-49b5-9d81-7de2cdabfc69", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "0e0c9ad6-ec83-48b3-87f9-c2710659821b", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "1ee2022a-e054-4f76-8c1a-60967e76b407", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": { - "id": "b07e8193-47cf-4395-a1f6-a5d4d7fa9b17", - "type": "terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "c8fa5c2a-1bd0-48d8-8c94-2ef8a06c4ce9", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/290a696b-5fba-45aa-a08c-0e15ae89e9c0" - } - }, - { - "id": "c8fa5c2a-1bd0-48d8-8c94-2ef8a06c4ce9", - "type": "container", - "attributes": { - "number": "GLDU1222600", - "seal_number": "d5103634ed1adbd4", - "created_at": "2023-01-24T00:14:28Z", - "ref_numbers": [ - "REF-889564" - ], - "pod_arrived_at": "2023-01-24T00:14:28Z", - "pod_discharged_at": "2023-01-24T00:14:28Z", - "final_destination_full_out_at": "2023-01-24T00:14:28Z", - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": 46679, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "holds_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "availability_known": true, - "available_for_pickup": false, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "290a696b-5fba-45aa-a08c-0e15ae89e9c0", - "type": "shipment" - } - }, - "pod_terminal": { - "data": null - }, - "transport_events": { - "data": [ - { - "id": "c1443820-304a-444b-bf42-c3d885dc8daa", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - - ] - } - } - }, - { - "id": "0e0c9ad6-ec83-48b3-87f9-c2710659821b", - "type": "port", - "attributes": { - "id": "0e0c9ad6-ec83-48b3-87f9-c2710659821b", - "name": "Port of Oakland", - "code": "USOAK", - "state_abbr": "CA", - "city": "Oakland", - "country_code": "US", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "1ee2022a-e054-4f76-8c1a-60967e76b407", - "type": "terminal", - "attributes": { - "id": "1ee2022a-e054-4f76-8c1a-60967e76b407", - "nickname": "SSA", - "name": "SSA Terminal", - "firms_code": "Z985" - }, - "relationships": { - "port": { - "data": { - "id": "0e0c9ad6-ec83-48b3-87f9-c2710659821b", - "type": "port" - } - } - } - }, - { - "id": "100c303e-79df-4301-9bf7-13f9e0c85851", - "type": "vessel", - "attributes": { - "name": "MSC CHANNE", - "imo": "9710438", - "mmsi": "255805864", - "latitude": -78.30435842851921, - "longitude": 25.471353799804547, - "nautical_speed_knots": 100, - "navigational_heading_degrees": 1, - "position_timestamp": "2023-06-05T19:46:18Z" - } - }, - { - "id": "c1443820-304a-444b-bf42-c3d885dc8daa", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_arrived", - "created_at": "2023-01-24T00:14:27Z", - "voyage_number": null, - "timestamp": "2023-01-24T00:14:27Z", - "data_source": "shipping_line", - "location_locode": "USOAK", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "290a696b-5fba-45aa-a08c-0e15ae89e9c0", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "c8fa5c2a-1bd0-48d8-8c94-2ef8a06c4ce9", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "100c303e-79df-4301-9bf7-13f9e0c85851", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "0e0c9ad6-ec83-48b3-87f9-c2710659821b", - "type": "port" - } - }, - "terminal": { - "data": { - "id": "1ee2022a-e054-4f76-8c1a-60967e76b407", - "type": "terminal" - } - } - } - } - ] -} -``` \ No newline at end of file diff --git a/docs/api-docs/useful-info/api-data-sources-availability.mdx b/docs/api-docs/useful-info/api-data-sources-availability.mdx deleted file mode 100644 index d566e1ae..00000000 --- a/docs/api-docs/useful-info/api-data-sources-availability.mdx +++ /dev/null @@ -1,242 +0,0 @@ ---- -title: API Data Sources and Availability. -description: Our platform gets data from variety of sources in order to create a complete view of a shipment and containers. However,some data is not universally available from all sources, and some data does not become available until certain milestones pass. This page will help you understand which data sources we support, and which data items should be universally expected by your code and which you need to code more defensively around. -og:title: API Data Sources Availability | Terminal49 API -og:description: Access availability of API data sources to enhance shipping logistics with Terminal49's comprehensive tools. ---- -# Data Sources - -- **Ocean carriers (aka steamship lines):** bill of lading/booking details, vessel eta, containers and milestones -- **Container terminal operators:** container availability, last free day, holds, fees etc -- **Container rail carriers:** container milestones via rail -- **AIS data:** vessel details and real-time location tracking (coming soon!) - -## Supported Ocean Carriers -View a complete list of supported carriers and attributes on [Google Sheets](https://docs.google.com/spreadsheets/d/1cWK8sNpkjY5V-KlXe1fHi8mU_at2HcJYqjCvGQgixQk/edit#gid=0) - -[![Carriers Screenshot](../../assets/images/carriers_screenshot.png "Carriers Screenshot")](https://docs.google.com/spreadsheets/d/1cWK8sNpkjY5V-KlXe1fHi8mU_at2HcJYqjCvGQgixQk/edit#gid=0) - - -## Ports and Terminals -Presently, the Terminal 49 api integrates with terminals at the following ports: -- Baltimore -- Boston -- Charleston -- Fraser Surrey (CA) -- Halifax (CA) -- Houston -- Jacksonville -- London Gateway (UK) -- Long Beach -- Los Angeles -- Miami -- Mobile -- New Orleans -- New York / New Jersey -- Oakland -- Philadelphia -- Port Everglades -- Portland -- Prince Rupert (CA) -- Savannah -- Seattle -- Southampton (UK) -- Tacoma -- Tampa -- Vancouver (CA) -- Virginia - -You can view a complete list of supported terminals and attributes on [Google Sheets](https://docs.google.com/spreadsheets/d/1cWK8sNpkjY5V-KlXe1fHi8mU_at2HcJYqjCvGQgixQk/edit#gid=1406366493) - -## Rail Carriers - -- BNSF Railway -- Canadian National Railway (CN) -- Canadian Pacific Railway (CP) -- CSX Transportation -- Norfolk Southern Railway (NS) -- Union Pacific Railroad (UP) - -## Known Issues (ocean) -Shipment data is populated from requests to the shipping lines. - -Below are a list of known issues with our data sources: - -### Cma-Cgm, APL, ANL -- No container weight -- No container seal number - -### Maersk, Sealand, Safmarine -- Shipment departure/arrival events are not always available depending on when BL is entered into system. -- No container seal number - -### Hamburg Süd -- No estimated departure time -- No container weight -- No container seal number - -### MSC -- No container seal number - -### Hapag Lloyd -- No container weight -- No container seal number - -### Evergreen -- All dates are provided as dates, not datetimes. We record and return them all as midnight at the location the event happened (when location is available) or midnight UTC. -- Only Dry, Reefer, and Flatpack container types are mapped to our system - -### COSCO -- No departure or arrival events. Does not affect departure/arrival times. - -### OOCL -- No container seal number - -### ONE -- Only Dry, and Reefer container types are mapped to our system - -### Yang-Ming -- When BL has multiple containers, the container weight returned is the average of the shipment. (i.e. the BL gross weight / number of containers) - -### Hyundai Merchant Marine -- No container type - -### ZIM -- No container weight -- No container seal number - -### Westwood Shipping -- No container weight -- Only Dry container types are mapped to our system - -{/* ## Rail Coverage - -Rail data is populated from requests to the rail carriers. Some carriers provide more data than others. - - -| Event | BNSF | Canadian National Railway | Canadian Pacific Railway | Norfolk Southern Railway | Union Pacific Railroad | CSX Transportation | -|-----------------------------|------|---------------------------|--------------------------|--------------------------|------------------------|---------------------| -| rail_loaded | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | -| rail_departed | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | -| rail_arrived | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| arrived_at_destination | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | -| rail_unloaded | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | -| train_passing | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | -| rail_interchange_delivered | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | -| rail_interchange_received | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | -| full_out | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | -| empty_in | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | -| full_in | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | -| empty_out | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | -| available | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| customs_release | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | -| not_available | | | | | | | -| holds_and_fees_changed | | | | | | | -| last_free_day_changed | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | - -In addition to some data not being provided by all carriers, there are a couple other known issues: - -- BNSF does not provide an event history -- ETAs are notoriously incorrect (we're working on a way to improve on what we get from the carriers) */} - -# Data Fields & Availability - -{/* These seem very out of date... many new properties added since we updated it years ago. Should we update it, or remove it? */} -{/* I went ahead and added the newest properties to Container Data */} - -Below is a list of data that can be retrieved via the API, including whether is is always available, or whether it is only supported by certain carriers (Carrier Dependent), certain Terminals (Terminal Dependent) or on certain types of journeys (Journey dependent). - -## Shipment Data -Shipment Data is the primary data that comes from the Carrier. It containers the details of the shipment retrieved from the Bill of Lading, and references multiple container objects. - -| Data | Availability | More details | Notes | -| ------ |-----|-----|-----| -| Port of Lading | Always | Port of Lading name, Port of Lading UN/LOCODE, Port of Lading Timezone | | -| Port of Discharge | Always | Port of Discharge name, Port of discharge UN/LOCODE,Port of Discharge Timezone | | -| Final Destination beyond Port of Discharge | Carrier dependent, Journey Dependent | Destination name, Destination UN/LOCODE, Destination UN/LOCODE, Destination Timezone | Only for shipments with inland moves provided by or booked by the carrier. | -| Listing of Container Numbers | Always | A list of container numbers with data attributes listed below | | -| Bill of Lading Number | Always (inputted by user) | BOL | | -| Shipping Line Details | Always | SCAC, SSL Name | | -| Voyage Details | Milestone-based | Vessel Name, Vessel IMO, Voyage Number | | -| Estimated Time of Departure | Carrier dependent | Timestamp | | -| Actual Time of Departure | Always | Timestamp | After departure | -| Estimated Time of Arrival at Port of Discharge | Carrier dependent | Timestamp | | -| Actual Time of Arrival at Port of Discharge | Always | Timestamp | Available after arrival | -| Estimated Time of Arrival at Final Destination | Carrier dependent, Journey dependent | Timestamp | Only for vessels with inland moves. | - - -## Container Data -At the container level, the following data is available. Container data is combined from all sources to create a single data view of the container. As such some of this data will only available when certain milestones have passed. - -| Data | Availability | More Details | Notes | -| ---------------- | ----------------- | ------------------------------------------------ | -------------------- | -| Container Number | Always | number | | -| Seal Number | Carrier dependent | number | | -| Equipment Type | Always | Dry, reefer, open top, flat rack, tank, hard top | Enumerated data type | -| Equipment length | Always | 20, 40, 45, 50 | Enumerated Data Type | -| Equipment height | Always | Standard, high cube | Enumerated Data Type | -| Weight | Carrier Dependent | Number | | -| Terminal Availability | Always | Availability Known, Availability for Pickup | | -| Holds | Terminal Dependent| Array of statuses | Each status includes the hold name (one of: customs, freight, TMF, other, USDA) and the status (pending, hold) as well as any extra description| -| Fees | Terminal Dependent| Array of statuses| Each status includes the fee type (one of: Demurrage, Exam, Other) and the amount the hold is for (a float)| -| Last Free Day | Terminal Dependent| Date of last free day | | -| Arrived at Port of Discharge | Always | Once Arrived | | -| Discharged at Port of Discharge | Always | Once discharged | | -| Full Out at Port of Discharge | Always | | | -| Full out at final destination | Journey Dependent | Only if non-port final destination | | -| Rail Loaded At Port of Discharge | Journey Dependent | Only if non-port final destination | | -| Rail Departed At Port of Discharge | Journey Dependent | Only if non-port final destination | | -| Rail Carrier Scac at Port of Discharge |Journey Dependent | Only if non-port final destination | | -| ETA for final destination | Carrier Dependent, Journey Dependent | Only if non-port final destination | | -| ATA for final destination | Journey Dependent | Only if non-port final destination | | -| LFD at final destination | Carrier Dependent, Journey Dependent | Only if non-port final destination | | - - -## Milestone Event Data -When a milestone passes, the Terminal49 API will ping one of your webhooks with a Milestone event. For each milestone, the following data is always provided. Container, Shipment, Vessel, Location and Terminal data will be provided as objects that contain the information listed above. - -| Milestone Data | Description | -| -------------- | ---------------------------------------------------------------- | -| Event Name | the name of the event. e.g. 'container.transport.vessel\_loaded' | -| Created At | when the event was created in our system | -| Timestamp | when the event occured | -| Timezone | Which timezone did the event occur in. | -| Voyage Number | the voyage number of the vessel | -| Container | A link to the Container Data | -| Shipment | A link to the Shipment Data | -| Vessel | Which vessel did the event occur on. | -| Location | Where did the event oocur. | -| Terminal | Which terminal did this occur at. | - -## Milestones Events Supported -A list of milestones that the API can track, as well as the event name used in the API. In future, further events may be supported. - -{/* Why do we have this here when we already have a list of events in multiple other places? */} - -| Milestone Event Name | Event Name | -| -------------------- | -------------------------------------- | -| Vessel Loaded | container.transport.vessel\_loaded | -| Vessel Departed | container.transport.vessel\_departed | -| Vessel Arrived | container.transport.vessel\_arrived | -| Vessel Berthed | container.transport.vessel\_berthed | -| Vessel Discharged | container.transport.vessel\_discharged | -| Empty Out | container.transport.empty\_oud | -| Full In | container.transport.full\_id | -| Full Out | container.transport.full\_out | -| Empty In | container.transport.empty\_id | -| Rail Departed | container.transport.rail\_departed | -| Rail Arrived | container.transport.rail\_arrived | -| Rail Loaded | container.transport.rail\_loaded | -| Rail Unloaded | container.transport.rail\_unloaded | -| Transshipment Arrived | container.transport.transshipment\_arrived | -| Transshipment Discharged | container.transport.transshipment\_discharged | -| Transshipment Loaded | container.transport.transshipment\_loaded | -| Transshipment Departed | container.transport.transshipment\_departed | -| Feeder Arrived | container.transport.feeder\_arrived | -| Feeder Discharged | container.transport.feeder\_discharged | -| Feeder Loaded | container.transport.feeder\_loaded | -| Feeder Departed | container.transport.feeder\_departed | -| Arrived at inland destination | container.transport.arrived\_at\_inland\_destination | -| Estimated Arrived at inland destination | container.transport.estimated.arrived\_at\_inland\_destination | -| Pickup LFD changed | container.pickup_lfd.changed | -| Available at Destination | container.transport.available | \ No newline at end of file diff --git a/docs/api-docs/useful-info/pricing.mdx b/docs/api-docs/useful-info/pricing.mdx deleted file mode 100644 index 96ecdcb7..00000000 --- a/docs/api-docs/useful-info/pricing.mdx +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Pricing -og:title: Pricing Information | Terminal49 API Documentation -og:description: Review Terminal49's pricing information for API access. Plan your logistics with transparent cost structures. ---- -View our [standard API pricing on our website](https://www.terminal49.com/pricing-plans#API-Section) \ No newline at end of file diff --git a/docs/api-docs/useful-info/test-numbers.mdx b/docs/api-docs/useful-info/test-numbers.mdx deleted file mode 100644 index 3de7ae12..00000000 --- a/docs/api-docs/useful-info/test-numbers.mdx +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Test Numbers -og:title: Test Numbers for API | Terminal49 API Documentation -og:description: Access test numbers for your API integrations with Terminal49 for accurate tracking simulations. ---- -## Overview -This page includes test `shipment` numbers and other information that you can use to make sure your integration works as planned. Use it to trigger different flows in your integration and ensure they are handled accordingly. - -## What are test numbers? - -We have created a variety of test numbers that you can use to make calls the Tracking Request API and create fake shipments. Each number has a specific purpose and alows you to test and integrate specific flows. You can create tests against these numbers and always execpt to receive the same response. -This is helpful when you want to test a specific webhooks notifications (ie: `shipment.eta_changed`, `shipment.vessel_arrived` etc) and you dont have a list of live shipments and containers that are in specific leg of their journey. - - -## Tracking Request API -Shipments are created by making requests to the Tracking Request API. -When using the API , ensure that: -- you set the test number in `request_number` attribute in the request body -- you set `scac` attribute as 'TEST' in the request body - - -## Test Numbers - -Number. | Use Case --------------------|--------- - TEST-TR-SUCCEEDED | test `tracking_request.succeeded` webhook - TEST-TR-FAILED | test `tracking_request.failed` webhook \ No newline at end of file diff --git a/docs/api-docs/useful-info/tracking-request-retrying.mdx b/docs/api-docs/useful-info/tracking-request-retrying.mdx deleted file mode 100644 index f7c22627..00000000 --- a/docs/api-docs/useful-info/tracking-request-retrying.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Tracking Request Retrying -og:title: Tracking Request Retrying | Terminal49 API Documentation -og:description: Retry tracking requests through Terminal49's API for uninterrupted shipment monitoring. ---- - -When you submit a tracking request your request is added to our queue to being checked at the shipping line. So what happens if the request doesn't go through correctly? - -If we are having difficulty connecting to the shipping line, or if we are unable to parse the response from the shipping line, we will keep retrying up to 14 times with an exponential back off. This process can take up to approximately 24 hours. You will not receive a `tracking_request.failed` webhook notification until we have exhausted the retries. - -If the shipping line returns a response that it cannot find the provided number then we will immediately return the `tracking_request.failed` event to your webhook. - -If you want to see the status of your tracking request you can make a [GET request](/api-docs/api-reference/tracking-requests/get-a-single-tracking-request) on it's `id` to see how many times it has retried, and what the most recent failure reason was. \ No newline at end of file diff --git a/docs/api-docs/useful-info/webhook-events-examples.mdx b/docs/api-docs/useful-info/webhook-events-examples.mdx deleted file mode 100644 index 65d9b7ed..00000000 --- a/docs/api-docs/useful-info/webhook-events-examples.mdx +++ /dev/null @@ -1,8276 +0,0 @@ ---- -title: Webhook Events Examples -og:title: Webhook Event Examples | Terminal49 API Documentation -og:description: Get examples of webhook events using Terminal49's API to enhance your tracking and notification systems. ---- -## container.created -```json -{ - "data": { - "id": "c6e6af71-f75d-49e3-9e79-50b719d8376e", - "type": "webhook_notification", - "attributes": { - "id": "c6e6af71-f75d-49e3-9e79-50b719d8376e", - "event": "container.created", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:18:43Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "8d86b03a-0ff7-4efe-b893-4feaf7d0bddc", - "type": "container_created_event" - } - }, - "webhook": { - "data": { - "id": "f1c5487c-ac3c-4ddc-ad77-5d1f32f75669", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "0b315c62-71f2-4c04-b252-88096d7f226f", - "type": "shipment", - "attributes": { - "created_at": "2022-10-21T20:18:36Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "MAEU221876618", - "normalized_number": "221876618", - "shipping_line_scac": "MAEU", - "shipping_line_name": "Maersk", - "shipping_line_short_name": "Maersk", - "customer_name": "Nienow LLC", - "port_of_lading_locode": "CNNGB", - "port_of_lading_name": "Ningbo", - "port_of_discharge_locode": null, - "port_of_discharge_name": null, - "pod_vessel_name": null, - "pod_vessel_imo": null, - "pod_voyage_number": null, - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": null, - "pol_timezone": "Asia/Shanghai", - "pod_eta_at": "2022-11-25T08:00:00Z", - "pod_original_eta_at": "2022-11-25T08:00:00Z", - "pod_ata_at": null, - "pod_timezone": null, - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "9b8a6dcc-2f14-4d2d-a91b-5a154ee6fbf8", - "type": "port" - } - }, - "port_of_discharge": { - "data": null - }, - "pod_terminal": { - "data": null - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "ede7ebb0-19e6-4bad-afcd-824bb8ca3cd7", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/e5a39855-f438-467a-9c18-ae91cd46cfaf" - } - }, - { - "id": "ede7ebb0-19e6-4bad-afcd-824bb8ca3cd7", - "type": "container", - "attributes": { - "number": "MRKU3700927", - "seal_number": null, - "created_at": "2022-10-21T20:18:36Z", - "ref_numbers": [ - - ], - "pod_arrived_at": null, - "pod_discharged_at": null, - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": null, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "pod_last_tracking_request_at": null, - "shipment_last_tracking_request_at": null, - "availability_known": false, - "pod_timezone": null, - "final_destination_timezone": null, - "empty_terminated_timezone": null - }, - "relationships": { - "shipment": { - "data": { - "id": "0b315c62-71f2-4c04-b252-88096d7f226f", - "type": "shipment" - } - }, - "pod_terminal": { - "data": null - }, - "transport_events": { - "data": [ - - ] - }, - "raw_events": { - "data": [ - - ] - } - } - }, - { - "id": "8d86b03a-0ff7-4efe-b893-4feaf7d0bddc", - "type": "container_created_event", - "attributes": { - "timestamp": "2022-10-21T20:18:36Z", - "timezone": "Etc/UTC" - }, - "relationships": { - "container": { - "data": { - "id": "ede7ebb0-19e6-4bad-afcd-824bb8ca3cd7", - "type": "container" - } - }, - "shipment": { - "data": { - "id": "0b315c62-71f2-4c04-b252-88096d7f226f", - "type": "shipment" - } - } - } - } - ] -} -``` - -## container.pod_terminal_changed -```json -{ - "data": { - "id": "262c2b9c-92f9-46ce-a3f7-e5cb14b1e9b3", - "type": "webhook_notification", - "attributes": { - "id": "262c2b9c-92f9-46ce-a3f7-e5cb14b1e9b3", - "event": "container.pod_terminal_changed", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:18:14Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "9df173e3-96b1-4b41-b0b2-a8459190ffc1", - "type": "container_pod_terminal_changed_event" - } - }, - "webhook": { - "data": { - "id": "33a10002-3bba-486d-b397-1361c4dd4858", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "ecab2629-f537-4a38-9099-cd78a3577fdc", - "type": "shipment", - "attributes": { - "created_at": "2022-10-20T17:02:14Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "CMDUSHZ5223740", - "normalized_number": "SHZ5223740", - "shipping_line_scac": "CMDU", - "shipping_line_name": "CMA CGM", - "shipping_line_short_name": "CMA CGM", - "customer_name": "Muller, Parisian and Bauch", - "port_of_lading_locode": "CNSHK", - "port_of_lading_name": "Shekou", - "port_of_discharge_locode": "USMIA", - "port_of_discharge_name": "Miami Seaport", - "pod_vessel_name": "CMA CGM OTELLO", - "pod_vessel_imo": "9299628", - "pod_voyage_number": "0PGDNE1MA", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": "2022-10-23T05:30:00Z", - "pol_atd_at": null, - "pol_timezone": "Asia/Shanghai", - "pod_eta_at": "2022-12-16T12:00:00Z", - "pod_original_eta_at": "2022-12-16T12:00:00Z", - "pod_ata_at": null, - "pod_timezone": "America/New_York", - "line_tracking_last_attempted_at": "2022-10-21T20:18:06Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "7cbb8ba7-66ca-4c6e-84e7-8cfa2686ae3b", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "ba9cc715-9b4c-4f78-a250-d68e26b23a5a", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "7db4d154-86c1-41e9-aa89-612eeb909f95", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "5820ed38-4b8b-4034-aefa-d5b5dbeb45e9", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/d08ffcbf-43c6-4f68-85c4-7f2199211723" - } - }, - { - "id": "5820ed38-4b8b-4034-aefa-d5b5dbeb45e9", - "type": "container", - "attributes": { - "number": "TGSU5023798", - "seal_number": null, - "created_at": "2022-10-20T17:02:14Z", - "ref_numbers": [ - - ], - "pod_arrived_at": null, - "pod_discharged_at": null, - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": null, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "pod_last_tracking_request_at": null, - "shipment_last_tracking_request_at": null, - "availability_known": false, - "pod_timezone": "America/New_York", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/New_York" - }, - "relationships": { - "shipment": { - "data": { - "id": "ecab2629-f537-4a38-9099-cd78a3577fdc", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "7db4d154-86c1-41e9-aa89-612eeb909f95", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "7ef9704f-1b7f-4eb5-b8c3-931fa68d2151", - "type": "transport_event" - }, - { - "id": "a5af8967-877e-4078-a5cd-200423ddcba2", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "9338ac85-2509-4d04-a0a4-5d4a572ea172", - "type": "raw_event" - }, - { - "id": "c10317c1-11b9-4d1b-b973-42d961da6340", - "type": "raw_event" - }, - { - "id": "daa39bfd-042f-487c-9f56-3d18fc7edb32", - "type": "raw_event" - }, - { - "id": "0c33d121-291f-4a5d-81d9-6a01f67c67bb", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "7db4d154-86c1-41e9-aa89-612eeb909f95", - "type": "terminal", - "attributes": { - "id": "7db4d154-86c1-41e9-aa89-612eeb909f95", - "nickname": "SFCT", - "name": "South Florida Container Terminal", - "firms_code": "N775", - "smdg_code": null, - "bic_facility_code": null, - "provided_data": { - "pickup_lfd": false, - "pickup_lfd_notes": "", - "available_for_pickup": false, - "fees_at_pod_terminal": false, - "holds_at_pod_terminal": false, - "pickup_appointment_at": false, - "location_at_pod_terminal": false, - "available_for_pickup_notes": "", - "fees_at_pod_terminal_notes": "", - "holds_at_pod_terminal_notes": "", - "pickup_appointment_at_notes": "", - "pod_full_out_chassis_number": false, - "location_at_pod_terminal_notes": "", - "pod_full_out_chassis_number_notes": "" - }, - "street": "302 Port Jersey Boulevard", - "city": "Jersey City", - "state": "New Jersey", - "state_abbr": "NJ", - "zip": "07305", - "country": "United States" - }, - "relationships": { - "port": { - "data": { - "id": "ba9cc715-9b4c-4f78-a250-d68e26b23a5a", - "type": "port" - } - } - } - }, - { - "id": "9df173e3-96b1-4b41-b0b2-a8459190ffc1", - "type": "container_pod_terminal_changed_event", - "attributes": { - "timestamp": "2022-10-21T20:18:14Z", - "data_source": "shipping_line" - }, - "relationships": { - "container": { - "data": { - "id": "5820ed38-4b8b-4034-aefa-d5b5dbeb45e9", - "type": "container" - } - }, - "terminal": { - "data": { - "id": "7db4d154-86c1-41e9-aa89-612eeb909f95", - "type": "terminal" - } - }, - "shipment": { - "data": { - "id": "ecab2629-f537-4a38-9099-cd78a3577fdc", - "type": "shipment" - } - } - } - } - ] -} -``` - -## container.transport.empty_in -```json -{ - "data": { - "id": "7e4e8acf-de36-401d-b3b9-55a5b16adbde", - "type": "webhook_notification", - "attributes": { - "id": "7e4e8acf-de36-401d-b3b9-55a5b16adbde", - "event": "container.transport.empty_in", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:18:58Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "b9936ca0-7e63-48db-8cad-e7d55d756530", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "b485aa7f-042b-49f8-8d81-31fa2c3c79eb", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "f7837cfa-2dc9-4f29-8562-1d1c8882eccd", - "type": "shipment", - "attributes": { - "created_at": "2022-09-23T16:35:47Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "LQ692823", - "normalized_number": "MEDULQ692823", - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "shipping_line_short_name": "MSC", - "customer_name": "Zulauf and Sons", - "port_of_lading_locode": "ITNAP", - "port_of_lading_name": "Naples", - "port_of_discharge_locode": "USNYC", - "port_of_discharge_name": "New York / New Jersey", - "pod_vessel_name": "MSC TIANJIN", - "pod_vessel_imo": "9285471", - "pod_voyage_number": "237W", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2022-09-23T06:30:00Z", - "pol_timezone": "Europe/Rome", - "pod_eta_at": "2022-10-14T04:00:00Z", - "pod_original_eta_at": "2022-10-14T04:00:00Z", - "pod_ata_at": "2022-10-14T13:54:01Z", - "pod_timezone": "America/New_York", - "line_tracking_last_attempted_at": "2022-10-21T20:18:48Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "bd523255-d320-489e-8710-1ec48ada8e45", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "74e47232-22a9-4cd5-aef6-30e21d826261", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "774573f6-beb7-4024-9bc4-a29f1d6eaf90", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "fe7e686c-1e34-4181-9333-9ed09c79b159", - "type": "container" - }, - { - "id": "4652f270-ce0c-4d89-89e1-bdd0993eac35", - "type": "container" - }, - { - "id": "8d460f4d-bf05-41fc-9daf-6afa1917a644", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/de8fcacc-0aed-4049-b324-aa65c9c2a765" - } - }, - { - "id": "8d460f4d-bf05-41fc-9daf-6afa1917a644", - "type": "container", - "attributes": { - "number": "FSCU8883322", - "seal_number": null, - "created_at": "2022-09-23T16:35:47Z", - "ref_numbers": [ - - ], - "pod_arrived_at": "2022-10-14T13:54:01Z", - "pod_discharged_at": "2022-10-14T04:00:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": null, - "pod_full_out_at": "2022-10-19T17:52:00Z", - "empty_terminated_at": "2022-10-21T04:00:00Z", - "terminal_checked_at": "2022-10-19T19:24:05Z", - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": "2022-10-20T04:00:00Z", - "pickup_appointment_at": "2022-10-19T16:00:00Z", - "pod_full_out_chassis_number": "OWNCHASSIS", - "location_at_pod_terminal": "COMMUNITY - OUT", - "pod_last_tracking_request_at": "2022-10-19T19:24:04Z", - "shipment_last_tracking_request_at": null, - "availability_known": true, - "pod_timezone": "America/New_York", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/New_York" - }, - "relationships": { - "shipment": { - "data": { - "id": "f7837cfa-2dc9-4f29-8562-1d1c8882eccd", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "774573f6-beb7-4024-9bc4-a29f1d6eaf90", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "91646567-40a8-42cf-93e1-6016b1274568", - "type": "transport_event" - }, - { - "id": "1313d847-66c0-425a-b797-4796275a8c41", - "type": "transport_event" - }, - { - "id": "61b832bf-031c-4296-8ca4-8b8f256a9fe1", - "type": "transport_event" - }, - { - "id": "747d7cc1-82b7-4183-bbb7-356b6d5e025d", - "type": "transport_event" - }, - { - "id": "0d2b94be-dea6-4d63-97c1-f21b7d5e767b", - "type": "transport_event" - }, - { - "id": "f53f143e-9f85-4fa2-a0df-6e25c4bfbc87", - "type": "transport_event" - }, - { - "id": "38cb8320-de6a-4385-a626-71d59473d5ff", - "type": "transport_event" - }, - { - "id": "b9936ca0-7e63-48db-8cad-e7d55d756530", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "e0e35bdd-fd6d-41cb-b39c-3d4f7c8ce758", - "type": "raw_event" - }, - { - "id": "5def6df9-2878-46cb-8648-69b157bd0993", - "type": "raw_event" - }, - { - "id": "20decd2c-3e2f-463d-a270-d249fd0bdbdb", - "type": "raw_event" - }, - { - "id": "260e6094-dfbb-40c1-ad90-37c1627b778d", - "type": "raw_event" - }, - { - "id": "264f4d56-7c1b-4550-830f-c26b1448cce1", - "type": "raw_event" - }, - { - "id": "1d20551a-dfe9-4584-baae-d0288f7342e8", - "type": "raw_event" - }, - { - "id": "c5baa1c6-0255-444f-afe6-65db202c33fb", - "type": "raw_event" - }, - { - "id": "09554878-a1b5-4c0f-972e-8250163a6be4", - "type": "raw_event" - }, - { - "id": "59974058-689b-4052-8598-592aa2c999fa", - "type": "raw_event" - }, - { - "id": "00081755-7c93-421a-9a0b-5adf01f1051e", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "74e47232-22a9-4cd5-aef6-30e21d826261", - "type": "port", - "attributes": { - "id": "74e47232-22a9-4cd5-aef6-30e21d826261", - "name": "New York / New Jersey", - "code": "USNYC", - "state_abbr": "NY", - "city": "New York", - "country_code": "US", - "latitude": "40.684996498", - "longitude": "-74.151115685", - "time_zone": "America/New_York" - } - }, - { - "id": "b9936ca0-7e63-48db-8cad-e7d55d756530", - "type": "transport_event", - "attributes": { - "event": "container.transport.empty_in", - "created_at": "2022-10-21T20:18:58Z", - "voyage_number": null, - "timestamp": "2022-10-21T04:00:00Z", - "data_source": "shipping_line", - "location_locode": "USNYC", - "timezone": "America/New_York" - }, - "relationships": { - "shipment": { - "data": { - "id": "f7837cfa-2dc9-4f29-8562-1d1c8882eccd", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "8d460f4d-bf05-41fc-9daf-6afa1917a644", - "type": "container" - } - }, - "vessel": { - "data": null - }, - "location": { - "data": { - "id": "74e47232-22a9-4cd5-aef6-30e21d826261", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - } - ] -} -``` - -## container.transport.empty_out -```json -{ - "data": { - "id": "6dded288-6b72-483a-9f33-c79aa8e9c1ff", - "type": "webhook_notification", - "attributes": { - "id": "6dded288-6b72-483a-9f33-c79aa8e9c1ff", - "event": "container.transport.empty_out", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:17:02Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "fb7533ea-7afc-4a7c-a831-0b36bd28bf26", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "feb4bb16-deff-4249-8fc6-5ae67c2fe8d2", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "1fe11df6-143d-4d6c-bbc8-b5963e19611f", - "type": "shipment", - "attributes": { - "created_at": "2022-10-21T20:16:02Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "SA00846884", - "normalized_number": "SA00846884", - "shipping_line_scac": "ACLU", - "shipping_line_name": "Atlantic Container Line", - "shipping_line_short_name": "ACL", - "customer_name": "Stracke Inc", - "port_of_lading_locode": "BEANR", - "port_of_lading_name": "Antwerp", - "port_of_discharge_locode": "USNYC", - "port_of_discharge_name": "New York / New Jersey", - "pod_vessel_name": null, - "pod_vessel_imo": null, - "pod_voyage_number": null, - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": "2022-11-04T13:00:00Z", - "pol_atd_at": null, - "pol_timezone": "Europe/Brussels", - "pod_eta_at": "2022-11-15T00:00:00Z", - "pod_original_eta_at": "2022-11-15T00:00:00Z", - "pod_ata_at": null, - "pod_timezone": "America/New_York", - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "fc6a6c8c-4f6f-459b-be6c-814d34ec312b", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "dfcc3bcd-a63d-4481-b68b-a91da48b5d79", - "type": "port" - } - }, - "pod_terminal": { - "data": null - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "3f92cb0c-b7b2-4f08-ae65-677fc4d7712d", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/6e9625a2-ea71-49ad-8441-13a3a44926f2" - } - }, - { - "id": "3f92cb0c-b7b2-4f08-ae65-677fc4d7712d", - "type": "container", - "attributes": { - "number": "GCNU8802957", - "seal_number": null, - "created_at": "2022-10-21T20:16:02Z", - "ref_numbers": [ - - ], - "pod_arrived_at": null, - "pod_discharged_at": null, - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": false, - "equipment_type": "reefer", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": null, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "pod_last_tracking_request_at": null, - "shipment_last_tracking_request_at": null, - "availability_known": false, - "pod_timezone": "America/New_York", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/New_York" - }, - "relationships": { - "shipment": { - "data": { - "id": "1fe11df6-143d-4d6c-bbc8-b5963e19611f", - "type": "shipment" - } - }, - "pod_terminal": { - "data": null - }, - "transport_events": { - "data": [ - { - "id": "fb7533ea-7afc-4a7c-a831-0b36bd28bf26", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "e453e33d-3ef7-4fdb-b012-e83ba5903466", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "2381793f-8f43-4bd1-a4e0-1135c322f441", - "type": "metro_area", - "attributes": { - "id": "2381793f-8f43-4bd1-a4e0-1135c322f441", - "name": "Antwerp Churchill Terminal", - "state_abbr": "Vlaanderen", - "code": "BEANT", - "latitude": "51.2806024", - "longitude": "4.3551883", - "country_code": "BE", - "time_zone": "Europe/Brussels" - } - }, - { - "id": "fb7533ea-7afc-4a7c-a831-0b36bd28bf26", - "type": "transport_event", - "attributes": { - "event": "container.transport.empty_out", - "created_at": "2022-10-21T20:16:02Z", - "voyage_number": null, - "timestamp": "2022-10-20T11:12:00Z", - "data_source": "shipping_line", - "location_locode": "BEANT", - "timezone": "Europe/Brussels" - }, - "relationships": { - "shipment": { - "data": { - "id": "1fe11df6-143d-4d6c-bbc8-b5963e19611f", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "3f92cb0c-b7b2-4f08-ae65-677fc4d7712d", - "type": "container" - } - }, - "vessel": { - "data": null - }, - "location": { - "data": { - "id": "2381793f-8f43-4bd1-a4e0-1135c322f441", - "type": "metro_area" - } - }, - "terminal": { - "data": null - } - } - } - ] -} -``` - -## container.transport.full_in -```json -{ - "data": { - "id": "63fb3158-375e-417f-a31e-baba60a17afa", - "type": "webhook_notification", - "attributes": { - "id": "63fb3158-375e-417f-a31e-baba60a17afa", - "event": "container.transport.full_in", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:18:14Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "83e82be7-1791-48cd-a595-e4c92c3ddd09", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "fad16f92-e418-49eb-b004-55eeff8e28c6", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "84aedf7a-a3ec-48e6-bc36-e3234454795c", - "type": "shipment", - "attributes": { - "created_at": "2022-10-20T17:02:14Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "CMDUSHZ5223740", - "normalized_number": "SHZ5223740", - "shipping_line_scac": "CMDU", - "shipping_line_name": "CMA CGM", - "shipping_line_short_name": "CMA CGM", - "customer_name": "Kris LLC", - "port_of_lading_locode": "CNSHK", - "port_of_lading_name": "Shekou", - "port_of_discharge_locode": "USMIA", - "port_of_discharge_name": "Miami Seaport", - "pod_vessel_name": "CMA CGM OTELLO", - "pod_vessel_imo": "9299628", - "pod_voyage_number": "0PGDNE1MA", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": "2022-10-23T05:30:00Z", - "pol_atd_at": null, - "pol_timezone": "Asia/Shanghai", - "pod_eta_at": "2022-12-16T12:00:00Z", - "pod_original_eta_at": "2022-12-16T12:00:00Z", - "pod_ata_at": null, - "pod_timezone": "America/New_York", - "line_tracking_last_attempted_at": "2022-10-21T20:18:06Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "57f55608-c9fb-47d1-8cd6-0e78b340061b", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "b802e728-e01a-400f-9687-81e9d7f4da51", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "21ff320e-ddb7-4199-8873-a819e9dcfc31", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "975be82b-d16a-4b0f-818a-ea1ba83c3fde", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/d08ffcbf-43c6-4f68-85c4-7f2199211723" - } - }, - { - "id": "975be82b-d16a-4b0f-818a-ea1ba83c3fde", - "type": "container", - "attributes": { - "number": "TGSU5023798", - "seal_number": null, - "created_at": "2022-10-20T17:02:14Z", - "ref_numbers": [ - - ], - "pod_arrived_at": null, - "pod_discharged_at": null, - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": null, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "pod_last_tracking_request_at": null, - "shipment_last_tracking_request_at": null, - "availability_known": false, - "pod_timezone": "America/New_York", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/New_York" - }, - "relationships": { - "shipment": { - "data": { - "id": "84aedf7a-a3ec-48e6-bc36-e3234454795c", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "21ff320e-ddb7-4199-8873-a819e9dcfc31", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "3a60833d-df16-438d-ad8e-5b3d9a1c44ed", - "type": "transport_event" - }, - { - "id": "83e82be7-1791-48cd-a595-e4c92c3ddd09", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "ffbab92a-7f6d-45f3-bfa1-c88a79feb19c", - "type": "raw_event" - }, - { - "id": "2323c45e-a4ab-42fc-95a7-5b4f14af6835", - "type": "raw_event" - }, - { - "id": "64bd26a3-8393-41bb-ab7c-64b8d705d054", - "type": "raw_event" - }, - { - "id": "2e08535f-7a3f-4dc1-87f9-939101e46d53", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "57f55608-c9fb-47d1-8cd6-0e78b340061b", - "type": "port", - "attributes": { - "id": "57f55608-c9fb-47d1-8cd6-0e78b340061b", - "name": "Shekou", - "code": "CNSHK", - "state_abbr": null, - "city": null, - "country_code": "CN", - "latitude": "22.459940331", - "longitude": "113.892910965", - "time_zone": "Asia/Shanghai" - } - }, - { - "id": "83e82be7-1791-48cd-a595-e4c92c3ddd09", - "type": "transport_event", - "attributes": { - "event": "container.transport.full_in", - "created_at": "2022-10-21T20:18:14Z", - "voyage_number": null, - "timestamp": "2022-10-20T16:29:00Z", - "data_source": "shipping_line", - "location_locode": "CNSHK", - "timezone": "Asia/Shanghai" - }, - "relationships": { - "shipment": { - "data": { - "id": "84aedf7a-a3ec-48e6-bc36-e3234454795c", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "975be82b-d16a-4b0f-818a-ea1ba83c3fde", - "type": "container" - } - }, - "vessel": { - "data": null - }, - "location": { - "data": { - "id": "57f55608-c9fb-47d1-8cd6-0e78b340061b", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - } - ] -} -``` - -## container.transport.full_out -```json -{ - "data": { - "id": "bef3aef4-6e81-4824-8bf6-44e1cffa41a7", - "type": "webhook_notification", - "attributes": { - "id": "bef3aef4-6e81-4824-8bf6-44e1cffa41a7", - "event": "container.transport.full_out", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:19:06Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "65f4a065-a9f3-4f2e-b060-0e3d857ed67f", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "715b8e22-2671-45a8-972c-76784feca537", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "e0afd8d9-a942-480b-8902-03aec602808d", - "type": "shipment", - "attributes": { - "created_at": "2022-09-12T02:12:39Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "MAEUGAP001939", - "normalized_number": "GAP001939", - "shipping_line_scac": "MAEU", - "shipping_line_name": "Maersk", - "shipping_line_short_name": "Maersk", - "customer_name": "Shields, Pollich and Stoltenberg", - "port_of_lading_locode": "CNYTN", - "port_of_lading_name": "Yantian", - "port_of_discharge_locode": "USSAV", - "port_of_discharge_name": "Savannah", - "pod_vessel_name": "GLEN CANYON", - "pod_vessel_imo": "9302097", - "pod_voyage_number": "003E", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2022-09-10T01:08:00Z", - "pol_timezone": "Asia/Shanghai", - "pod_eta_at": "2022-10-19T10:00:00Z", - "pod_original_eta_at": "2022-10-18T10:00:00Z", - "pod_ata_at": "2022-10-19T10:00:00Z", - "pod_timezone": "America/New_York", - "line_tracking_last_attempted_at": "2022-10-21T20:19:02Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": "2022-10-21T20:19:06Z", - "line_tracking_stopped_reason": "all_containers_terminated" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "cdf4a74f-5c13-48f0-92e7-4a7704d2030f", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "f15826a5-d826-4845-8aaa-9f295b36397b", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "db19e898-22b3-44a9-ba61-3a4dbf4018e6", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "2ca3f310-4d0c-4b4f-8dcd-8b8d19f60fb8", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/4a97efa3-2383-41b8-87f3-3ae1fe81d429" - } - }, - { - "id": "2ca3f310-4d0c-4b4f-8dcd-8b8d19f60fb8", - "type": "container", - "attributes": { - "number": "MSKU8532556", - "seal_number": null, - "created_at": "2022-09-12T02:12:39Z", - "ref_numbers": [ - - ], - "pod_arrived_at": "2022-10-19T10:00:00Z", - "pod_discharged_at": "2022-10-20T06:01:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": null, - "pod_full_out_at": "2022-10-21T15:05:00Z", - "empty_terminated_at": null, - "terminal_checked_at": "2022-10-21T03:31:42Z", - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "Yard", - "pod_last_tracking_request_at": "2022-10-21T03:31:29Z", - "shipment_last_tracking_request_at": null, - "availability_known": true, - "pod_timezone": "America/New_York", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/New_York" - }, - "relationships": { - "shipment": { - "data": { - "id": "e0afd8d9-a942-480b-8902-03aec602808d", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "db19e898-22b3-44a9-ba61-3a4dbf4018e6", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "b9c685dc-0556-4b68-a9d5-f4747fcb9611", - "type": "transport_event" - }, - { - "id": "cab0d40f-ce6f-4170-bf08-d573a484944e", - "type": "transport_event" - }, - { - "id": "aa4ef47b-77b2-42c7-b81f-3dc2b1ead4a8", - "type": "transport_event" - }, - { - "id": "5f75e4b6-f139-4314-930c-e79efdd7b254", - "type": "transport_event" - }, - { - "id": "693b3062-6520-4390-a827-2dd390dbbc44", - "type": "transport_event" - }, - { - "id": "41270e3a-ee02-41c1-9135-3fb174bac54b", - "type": "transport_event" - }, - { - "id": "65f4a065-a9f3-4f2e-b060-0e3d857ed67f", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "a74dbeaf-8a2f-4409-8580-d049f131c7ae", - "type": "raw_event" - }, - { - "id": "0b500fb5-1613-4066-8b42-33d9d07e021f", - "type": "raw_event" - }, - { - "id": "3072849e-3031-427d-9e96-aa5ec6b8ca4f", - "type": "raw_event" - }, - { - "id": "c6e6023b-1b50-4ea5-aaf1-b6b9f1c184ef", - "type": "raw_event" - }, - { - "id": "7bccb807-404c-48c9-93df-3f3dcc754eb2", - "type": "raw_event" - }, - { - "id": "8226d324-42b0-480e-bc15-0d34bc73fde5", - "type": "raw_event" - }, - { - "id": "fdf27063-1a8c-48e7-afae-dc64d48346fb", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "f15826a5-d826-4845-8aaa-9f295b36397b", - "type": "port", - "attributes": { - "id": "f15826a5-d826-4845-8aaa-9f295b36397b", - "name": "Savannah", - "code": "USSAV", - "state_abbr": "GA", - "city": "Savannah", - "country_code": "US", - "latitude": "32.128923976", - "longitude": "-81.140998396", - "time_zone": "America/New_York" - } - }, - { - "id": "db19e898-22b3-44a9-ba61-3a4dbf4018e6", - "type": "terminal", - "attributes": { - "id": "db19e898-22b3-44a9-ba61-3a4dbf4018e6", - "nickname": "GCT", - "name": "Garden City Terminals", - "firms_code": "L737", - "smdg_code": null, - "bic_facility_code": null, - "provided_data": { - "pickup_lfd": false, - "pickup_lfd_notes": "", - "available_for_pickup": false, - "fees_at_pod_terminal": false, - "holds_at_pod_terminal": false, - "pickup_appointment_at": false, - "location_at_pod_terminal": false, - "available_for_pickup_notes": "", - "fees_at_pod_terminal_notes": "", - "holds_at_pod_terminal_notes": "", - "pickup_appointment_at_notes": "", - "pod_full_out_chassis_number": false, - "location_at_pod_terminal_notes": "", - "pod_full_out_chassis_number_notes": "" - }, - "street": "701 New Dock Street Berths 212-225", - "city": "Terminal Island", - "state": "California", - "state_abbr": "CA", - "zip": "90731", - "country": "United States" - }, - "relationships": { - "port": { - "data": { - "id": "f15826a5-d826-4845-8aaa-9f295b36397b", - "type": "port" - } - } - } - }, - { - "id": "65f4a065-a9f3-4f2e-b060-0e3d857ed67f", - "type": "transport_event", - "attributes": { - "event": "container.transport.full_out", - "created_at": "2022-10-21T20:19:06Z", - "voyage_number": null, - "timestamp": "2022-10-21T15:05:00Z", - "data_source": "shipping_line", - "location_locode": "USSAV", - "timezone": "America/New_York" - }, - "relationships": { - "shipment": { - "data": { - "id": "e0afd8d9-a942-480b-8902-03aec602808d", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "2ca3f310-4d0c-4b4f-8dcd-8b8d19f60fb8", - "type": "container" - } - }, - "vessel": { - "data": null - }, - "location": { - "data": { - "id": "f15826a5-d826-4845-8aaa-9f295b36397b", - "type": "port" - } - }, - "terminal": { - "data": { - "id": "db19e898-22b3-44a9-ba61-3a4dbf4018e6", - "type": "terminal" - } - } - } - } - ] -} -``` - -## container.transport.rail_arrived -```json -{ - "data": { - "id": "83cc76e6-64c9-4a47-ab7a-b1a796016041", - "type": "webhook_notification", - "attributes": { - "id": "83cc76e6-64c9-4a47-ab7a-b1a796016041", - "event": "container.transport.rail_arrived", - "delivery_status": "pending", - "created_at": "2022-10-21T20:18:00Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "7120723d-7bbd-43a8-bbd2-69d742ba76ae", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "72e27eda-f3ff-47f4-9ebd-04e4c82e411f", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "a8a97ac7-648b-42fa-9629-ecbd323a2cd6", - "type": "shipment", - "attributes": { - "created_at": "2022-09-28T14:19:08Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "OOLU2706578920", - "normalized_number": "2706578920", - "shipping_line_scac": "OOLU", - "shipping_line_name": "Orient Overseas Container Line", - "shipping_line_short_name": "OOCL", - "customer_name": "Heller, Hansen and Schumm", - "port_of_lading_locode": "TWKHH", - "port_of_lading_name": "Kaohsiung", - "port_of_discharge_locode": "USLGB", - "port_of_discharge_name": "Long Beach", - "pod_vessel_name": "COSCO ENGLAND", - "pod_vessel_imo": "9516428", - "pod_voyage_number": "054E", - "destination_locode": "USEWI", - "destination_name": "Elwood", - "destination_timezone": "America/Chicago", - "destination_ata_at": "2022-10-21T17:43:00Z", - "destination_eta_at": "2022-10-18T09:36:00Z", - "pol_etd_at": null, - "pol_atd_at": "2022-09-27T02:05:00Z", - "pol_timezone": "Asia/Taipei", - "pod_eta_at": "2022-10-12T14:00:00Z", - "pod_original_eta_at": "2022-10-10T15:00:00Z", - "pod_ata_at": "2022-10-12T13:26:00Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": "2022-10-21T20:17:48Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "0233b87d-833e-45bb-ae74-83fd69200d81", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "f8361d18-09c2-4aff-a933-ca2c22919532", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "d9bf35cd-3bb6-4235-96ca-b57665173c11", - "type": "terminal" - } - }, - "destination": { - "data": { - "id": "ad3c66a7-2580-4757-90f4-5f28c2892468", - "type": "metro_area" - } - }, - "destination_terminal": { - "data": { - "id": "cac87536-25db-4d4f-bb15-3a674d67d31f", - "type": "rail_terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "e25ff651-f58f-4165-8e97-aa2ad73027be", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/7ffaff8a-8004-4c02-8acf-3d744708e0b4" - } - }, - { - "id": "e25ff651-f58f-4165-8e97-aa2ad73027be", - "type": "container", - "attributes": { - "number": "OOLU6213464", - "seal_number": null, - "created_at": "2022-09-28T14:19:08Z", - "ref_numbers": [ - - ], - "pod_arrived_at": "2022-10-12T13:26:00Z", - "pod_discharged_at": "2022-10-12T22:27:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - { - "status": "hold", - "name": "other", - "description": "ONDOCK" - }, - { - "status": "hold", - "name": "other", - "description": "CTF_CONTAINER_HOLD" - }, - { - "status": "hold", - "name": "freight", - "description": "FREIGHT_BL_HOLD" - } - ], - "available_for_pickup": false, - "equipment_type": "reefer", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": 56879, - "pod_full_out_at": "2022-10-14T16:21:00Z", - "empty_terminated_at": null, - "terminal_checked_at": "2022-10-14T20:50:59Z", - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "GROUNDED", - "pod_last_tracking_request_at": "2022-10-14T20:50:59Z", - "shipment_last_tracking_request_at": null, - "availability_known": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": "America/Chicago", - "empty_terminated_timezone": "America/Chicago" - }, - "relationships": { - "shipment": { - "data": { - "id": "a8a97ac7-648b-42fa-9629-ecbd323a2cd6", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "d9bf35cd-3bb6-4235-96ca-b57665173c11", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "78941fc1-91e0-440a-84d2-b87106a854ba", - "type": "transport_event" - }, - { - "id": "60898751-f616-45b1-942e-a65aa7c9ee96", - "type": "transport_event" - }, - { - "id": "7bc2b53a-7638-47d8-9820-0cfbf1ef5300", - "type": "transport_event" - }, - { - "id": "8cb669fb-9dde-4f2c-b35e-47c460f3e650", - "type": "transport_event" - }, - { - "id": "f33ccf00-c8e8-4712-a323-2f43245e5fe6", - "type": "transport_event" - }, - { - "id": "20a936fb-b0e2-49ff-a2a7-7ea3c3ae91a6", - "type": "transport_event" - }, - { - "id": "f5392045-0e24-46cb-8984-72642844d373", - "type": "transport_event" - }, - { - "id": "a3bf7950-1cf6-43fb-859d-ef35610b7d26", - "type": "transport_event" - }, - { - "id": "cb8f07dd-8be4-4743-95d9-704da3d788de", - "type": "transport_event" - }, - { - "id": "02f73f8c-26ad-48e3-b4a4-e127ded9dfc6", - "type": "transport_event" - }, - { - "id": "ce57263a-d817-46fd-9f26-b9290d555b47", - "type": "transport_event" - }, - { - "id": "d5274b77-2829-4b03-b466-d6bb3685811a", - "type": "transport_event" - }, - { - "id": "8bb29b9a-f49a-42a3-9381-7bd9fe1245b9", - "type": "transport_event" - }, - { - "id": "7120723d-7bbd-43a8-bbd2-69d742ba76ae", - "type": "transport_event" - }, - { - "id": "7dc68b35-48f4-4941-b7a4-3b6c5ee63871", - "type": "transport_event" - }, - { - "id": "7233a3af-1d0b-4725-a15c-1e303f5b5e49", - "type": "transport_event" - }, - { - "id": "57687dd8-b5f1-4fd8-bf77-e9e801991084", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "a0fd66f6-6787-45c4-8b98-b1534f8b0598", - "type": "raw_event" - }, - { - "id": "5caa8987-0847-4453-a9fd-df68d963785f", - "type": "raw_event" - }, - { - "id": "b5efac1c-c773-46c7-ae41-cc7e7bc3059c", - "type": "raw_event" - }, - { - "id": "35074992-a17b-4a85-97f2-9ffe9620bc5f", - "type": "raw_event" - }, - { - "id": "209702ed-0fad-43d9-a092-20b5a46b5ad6", - "type": "raw_event" - }, - { - "id": "9fe50c8e-5584-4491-a8d6-c30ef8cffa11", - "type": "raw_event" - }, - { - "id": "cd36808d-3dc7-486f-b0a8-082b879f115c", - "type": "raw_event" - }, - { - "id": "a7c6ead4-e104-4b51-aa28-fc4b7cc95494", - "type": "raw_event" - }, - { - "id": "61c4afd6-d5f5-4eec-b8fd-e3fd2bcab381", - "type": "raw_event" - }, - { - "id": "fa6d887d-77ed-49b3-8ce4-4b7856d55a70", - "type": "raw_event" - }, - { - "id": "59f40a9b-cf7a-494e-a251-d47ae1bc28c0", - "type": "raw_event" - }, - { - "id": "0eb7e500-94cc-4a50-ac16-6d207edd2a48", - "type": "raw_event" - }, - { - "id": "c71761fc-2330-455e-b4b2-20b5b4e35800", - "type": "raw_event" - }, - { - "id": "e1e33733-23f3-44a7-a683-c79f7e1c36a6", - "type": "raw_event" - }, - { - "id": "6ed36430-f0a6-49eb-96dc-b67bebd58ffa", - "type": "raw_event" - }, - { - "id": "d0c88cb3-ca75-4216-bfb6-a3305156bb29", - "type": "raw_event" - }, - { - "id": "c3d046cc-56ae-487a-9ac3-5d3b00edb5a4", - "type": "raw_event" - }, - { - "id": "ec3f15e1-b342-454f-8d85-8d7495ca3767", - "type": "raw_event" - }, - { - "id": "311a1b33-2341-4342-b5e7-6b42bdb4d5d1", - "type": "raw_event" - }, - { - "id": "c6a819c6-97db-4894-ab70-6be34f1cf2a3", - "type": "raw_event" - }, - { - "id": "1a57d88a-789a-44ad-a1ec-f97cf8561090", - "type": "raw_event" - }, - { - "id": "36d04b27-9f8d-493c-8df6-d435f0f50293", - "type": "raw_event" - }, - { - "id": "7af8c6df-45a6-4fb4-8bd6-cda6726a428a", - "type": "raw_event" - }, - { - "id": "ad3aa77e-4d12-4772-bf01-ca0ca98d91ed", - "type": "raw_event" - }, - { - "id": "e86c02d2-577d-49f8-9c86-5632b8ab0fe7", - "type": "raw_event" - }, - { - "id": "f159db75-2e5e-45e9-bffc-3879bca1cc5c", - "type": "raw_event" - }, - { - "id": "f605cfd9-a956-4051-b209-b83b83c459c7", - "type": "raw_event" - }, - { - "id": "e12a406b-a3f6-40e3-a771-31e7596f94b8", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "ad3c66a7-2580-4757-90f4-5f28c2892468", - "type": "metro_area", - "attributes": { - "id": "ad3c66a7-2580-4757-90f4-5f28c2892468", - "name": "Elwood", - "state_abbr": "IL", - "code": "USEWI", - "latitude": "41.4039201", - "longitude": "-88.1117242", - "country_code": "US", - "time_zone": "America/Chicago" - } - }, - { - "id": "cac87536-25db-4d4f-bb15-3a674d67d31f", - "type": "rail_terminal", - "attributes": { - "id": "cac87536-25db-4d4f-bb15-3a674d67d31f", - "nickname": "BNSF", - "name": "BNSF - Logistics Park Chicago (LPC) Intermodal Facility", - "city": "Elwood", - "firms_code": "H572" - }, - "relationships": { - "metro_area": { - "data": { - "id": "ad3c66a7-2580-4757-90f4-5f28c2892468", - "type": "metro_area" - } - }, - "port": { - "data": null - } - } - }, - { - "id": "7120723d-7bbd-43a8-bbd2-69d742ba76ae", - "type": "transport_event", - "attributes": { - "event": "container.transport.rail_arrived", - "created_at": "2022-10-21T20:18:00Z", - "voyage_number": null, - "timestamp": "2022-10-21T17:43:00Z", - "data_source": "shipping_line", - "location_locode": "USEWI", - "timezone": "America/Chicago" - }, - "relationships": { - "shipment": { - "data": { - "id": "a8a97ac7-648b-42fa-9629-ecbd323a2cd6", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "e25ff651-f58f-4165-8e97-aa2ad73027be", - "type": "container" - } - }, - "vessel": { - "data": null - }, - "location": { - "data": { - "id": "ad3c66a7-2580-4757-90f4-5f28c2892468", - "type": "metro_area" - } - }, - "terminal": { - "data": { - "id": "cac87536-25db-4d4f-bb15-3a674d67d31f", - "type": "rail_terminal" - } - } - } - } - ] -} -``` - -## container.transport.rail_departed -```json -{ - "data": { - "id": "176364d2-7f63-4382-8fba-da24e3c14057", - "type": "webhook_notification", - "attributes": { - "id": "176364d2-7f63-4382-8fba-da24e3c14057", - "event": "container.transport.rail_departed", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:15:29Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "7ee8aae1-14da-491a-81fc-6c98ed89775f", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "90c5b9c3-366f-49fb-bbf5-df024f1848d4", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "015c921e-ecdf-4491-982e-89152288f3ae", - "type": "shipment", - "attributes": { - "created_at": "2022-09-20T08:00:59Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "2706772870", - "normalized_number": "2706772870", - "shipping_line_scac": "OOLU", - "shipping_line_name": "Orient Overseas Container Line", - "shipping_line_short_name": "OOCL", - "customer_name": "Brekke Inc", - "port_of_lading_locode": "CNYTN", - "port_of_lading_name": "Yantian", - "port_of_discharge_locode": "USLGB", - "port_of_discharge_name": "Long Beach", - "pod_vessel_name": "COSCO ENGLAND", - "pod_vessel_imo": "9516428", - "pod_voyage_number": "054E", - "destination_locode": "USEWI", - "destination_name": "Elwood", - "destination_timezone": "America/Chicago", - "destination_ata_at": null, - "destination_eta_at": "2022-10-25T00:50:00Z", - "pol_etd_at": "2022-09-25T10:00:00Z", - "pol_atd_at": "2022-09-25T10:41:00Z", - "pol_timezone": "Asia/Shanghai", - "pod_eta_at": "2022-10-12T14:00:00Z", - "pod_original_eta_at": "2022-10-09T15:00:00Z", - "pod_ata_at": "2022-10-12T13:26:00Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": "2022-10-21T20:15:13Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "fcc475e5-9625-4632-815e-bd84db28ed4e", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "39425403-9982-47f1-9988-6b73f400b9ba", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "b0765575-ee97-45e4-a2c8-e9a11c969b37", - "type": "terminal" - } - }, - "destination": { - "data": { - "id": "239ca895-c67b-4563-b496-821833d03272", - "type": "metro_area" - } - }, - "destination_terminal": { - "data": { - "id": "e8e91a82-44ac-4690-baed-2f0998c2d303", - "type": "rail_terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "600785bd-04b3-48be-b2a5-37264ba9fc74", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/0372dba4-f153-44e1-a2d0-e6f444658b60" - } - }, - { - "id": "600785bd-04b3-48be-b2a5-37264ba9fc74", - "type": "container", - "attributes": { - "number": "OOCU7853330", - "seal_number": null, - "created_at": "2022-09-20T08:00:59Z", - "ref_numbers": [ - - ], - "pod_arrived_at": "2022-10-12T13:26:00Z", - "pod_discharged_at": "2022-10-14T02:56:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - { - "status": "hold", - "name": "other", - "description": "ONDOCK" - }, - { - "status": "hold", - "name": "other", - "description": "CTF_CONTAINER_HOLD" - }, - { - "status": "hold", - "name": "freight", - "description": "FREIGHT_BL_HOLD" - } - ], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": 45101, - "pod_full_out_at": "2022-10-20T20:41:00Z", - "empty_terminated_at": null, - "terminal_checked_at": "2022-10-21T00:19:37Z", - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "GROUNDED", - "pod_last_tracking_request_at": "2022-10-21T00:19:36Z", - "shipment_last_tracking_request_at": null, - "availability_known": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": "America/Chicago", - "empty_terminated_timezone": "America/Chicago" - }, - "relationships": { - "shipment": { - "data": { - "id": "015c921e-ecdf-4491-982e-89152288f3ae", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "b0765575-ee97-45e4-a2c8-e9a11c969b37", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "521f72b5-aee1-4857-859d-a03c6a1d79f1", - "type": "transport_event" - }, - { - "id": "ea94574e-dab4-4751-90f7-32be994c15a2", - "type": "transport_event" - }, - { - "id": "95dd1b3c-04e2-4587-85c3-9458684792e8", - "type": "transport_event" - }, - { - "id": "6791ad37-3316-4f27-b3f4-78be3411e791", - "type": "transport_event" - }, - { - "id": "b87c94b6-237b-4140-a3a0-56b10f402a2a", - "type": "transport_event" - }, - { - "id": "fa4d115d-6cfc-4584-8658-1175bf7cb31e", - "type": "transport_event" - }, - { - "id": "278a247c-6561-4b9a-a251-27024ffef12b", - "type": "transport_event" - }, - { - "id": "24dca556-e1be-4963-84e0-dec6e4419dd8", - "type": "transport_event" - }, - { - "id": "7ee8aae1-14da-491a-81fc-6c98ed89775f", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "f8ce10b7-eed3-46c5-9ee9-91a23dbb70b1", - "type": "raw_event" - }, - { - "id": "c84461fe-2920-4d06-8868-4f2609b8a773", - "type": "raw_event" - }, - { - "id": "0d387d25-6fc1-4e29-a0df-2cd0b09a46e6", - "type": "raw_event" - }, - { - "id": "ac549216-841e-4d24-ab39-605b01ed8843", - "type": "raw_event" - }, - { - "id": "e12200c1-a077-460f-bac7-907683198b8b", - "type": "raw_event" - }, - { - "id": "3b8d212c-973a-42b6-b773-aa0ce4716e89", - "type": "raw_event" - }, - { - "id": "fd40180b-9aff-4d17-b508-4bfa6d205f6d", - "type": "raw_event" - }, - { - "id": "797a3eb1-d8cd-4011-ae63-bcd4437ea402", - "type": "raw_event" - }, - { - "id": "864089e6-61dd-4533-9d27-dc639bda8eee", - "type": "raw_event" - }, - { - "id": "1cbd85c2-177a-4ac1-bc25-dab6970da97c", - "type": "raw_event" - }, - { - "id": "cb5e6908-5add-4e99-b386-eb45a4bcf1f4", - "type": "raw_event" - }, - { - "id": "8fbe4b5a-dd3c-425e-96fd-d796b016792a", - "type": "raw_event" - }, - { - "id": "1dcaaa69-4aa0-4a30-849a-8e1e4b16c885", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "9b98ee7b-6e2a-4eaa-9d23-2e0b8e911e92", - "type": "port", - "attributes": { - "id": "9b98ee7b-6e2a-4eaa-9d23-2e0b8e911e92", - "name": "Los Angeles", - "code": "USLAX", - "state_abbr": "CA", - "city": "Los Angeles", - "country_code": "US", - "latitude": "33.728193631", - "longitude": "-118.255820307", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "7ee8aae1-14da-491a-81fc-6c98ed89775f", - "type": "transport_event", - "attributes": { - "event": "container.transport.rail_departed", - "created_at": "2022-10-21T20:15:29Z", - "voyage_number": null, - "timestamp": "2022-10-21T19:03:00Z", - "data_source": "shipping_line", - "location_locode": "USLAX", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "015c921e-ecdf-4491-982e-89152288f3ae", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "600785bd-04b3-48be-b2a5-37264ba9fc74", - "type": "container" - } - }, - "vessel": { - "data": null - }, - "location": { - "data": { - "id": "9b98ee7b-6e2a-4eaa-9d23-2e0b8e911e92", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - } - ] -} -``` - -## container.transport.rail_loaded -```json -{ - "data": { - "id": "dc507a07-9749-40b5-8481-fa4c539df722", - "type": "webhook_notification", - "attributes": { - "id": "dc507a07-9749-40b5-8481-fa4c539df722", - "event": "container.transport.rail_loaded", - "delivery_status": "succeeded", - "created_at": "2022-10-21T19:29:49Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "4c6c85eb-79bc-4f7c-ad66-962a6ab3a506", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "22cd79b4-3d37-4f34-a990-3f378760dd89", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "93921adf-af96-4096-9797-2abea7e95e79", - "type": "shipment", - "attributes": { - "created_at": "2022-10-04T22:00:30Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "6344045750", - "normalized_number": "6344045750", - "shipping_line_scac": "COSU", - "shipping_line_name": "COSCO", - "shipping_line_short_name": "COSCO", - "customer_name": "Hilll, Boyle and Hagenes", - "port_of_lading_locode": "CNSGH", - "port_of_lading_name": "Shanghai", - "port_of_discharge_locode": "CAVAN", - "port_of_discharge_name": "Vancouver", - "pod_vessel_name": "APL COLUMBUS", - "pod_vessel_imo": "9597525", - "pod_voyage_number": "0TN7VS1MA", - "destination_locode": "USCHI", - "destination_name": "Chicago", - "destination_timezone": "America/Chicago", - "destination_ata_at": null, - "destination_eta_at": "2022-10-30T02:50:00Z", - "pol_etd_at": null, - "pol_atd_at": "2022-09-23T00:17:00Z", - "pol_timezone": "Asia/Shanghai", - "pod_eta_at": "2022-10-19T13:00:00Z", - "pod_original_eta_at": "2022-10-18T12:00:00Z", - "pod_ata_at": "2022-10-19T13:49:00Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": "2022-10-21T19:29:46Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "6ec66cea-7a35-4522-98cd-52246198502b", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "a1edd1e9-61b0-429c-a05a-31ac8d9d2b8b", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "c989c2e6-c933-4ca9-98a5-a0418254b218", - "type": "terminal" - } - }, - "destination": { - "data": { - "id": "83b8c829-08d7-4b02-be09-a515b33f4237", - "type": "metro_area" - } - }, - "destination_terminal": { - "data": { - "id": "79fdf215-b32b-4a7b-99c5-1e44df0dcd76", - "type": "rail_terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "3be28cd9-165f-4af1-827a-902bef0147d0", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/5d990a0a-a854-4bf2-acd4-abc96d98da29" - } - }, - { - "id": "3be28cd9-165f-4af1-827a-902bef0147d0", - "type": "container", - "attributes": { - "number": "FFAU3144344", - "seal_number": "22626037", - "created_at": "2022-10-04T22:00:30Z", - "ref_numbers": [ - - ], - "pod_arrived_at": "2022-10-19T13:49:00Z", - "pod_discharged_at": "2022-10-20T20:25:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": 35598, - "pod_full_out_at": "2022-10-21T18:30:00Z", - "empty_terminated_at": null, - "terminal_checked_at": "2022-10-21T18:24:07Z", - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "RAIL", - "pod_last_tracking_request_at": "2022-10-21T18:24:07Z", - "shipment_last_tracking_request_at": null, - "availability_known": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": "America/Chicago", - "empty_terminated_timezone": "America/Chicago" - }, - "relationships": { - "shipment": { - "data": { - "id": "93921adf-af96-4096-9797-2abea7e95e79", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "c989c2e6-c933-4ca9-98a5-a0418254b218", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "462c6724-d4fc-4b3f-ae7e-1bb2f4ed9321", - "type": "transport_event" - }, - { - "id": "afa7595d-e6ea-4e99-8af9-a8d6d3269adb", - "type": "transport_event" - }, - { - "id": "5e36ae0c-bf1d-4993-8f58-cf2b3efc440a", - "type": "transport_event" - }, - { - "id": "6e4b9563-5b52-4d45-b3c6-a5111c14564d", - "type": "transport_event" - }, - { - "id": "ad1a0236-d071-41ac-bad4-dacecc245fc1", - "type": "transport_event" - }, - { - "id": "6195bd49-f61d-4f7e-9587-0ee03541b1d7", - "type": "transport_event" - }, - { - "id": "4353904b-a40b-494a-a13b-ad85141082d5", - "type": "transport_event" - }, - { - "id": "4c6c85eb-79bc-4f7c-ad66-962a6ab3a506", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "45f8bc1e-9a29-4505-b534-f6832a62c516", - "type": "raw_event" - }, - { - "id": "c72961bd-22b2-4940-bca2-5c17e97faee1", - "type": "raw_event" - }, - { - "id": "3b955f74-7f88-4b00-8aa1-e952bb914172", - "type": "raw_event" - }, - { - "id": "702ddb53-81d3-426a-b582-ac1f6cc40135", - "type": "raw_event" - }, - { - "id": "26b396c1-b220-40d4-af89-9ae4f69c359d", - "type": "raw_event" - }, - { - "id": "b1f05846-e074-4bd1-8e7b-e2eee8683f00", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "a1edd1e9-61b0-429c-a05a-31ac8d9d2b8b", - "type": "port", - "attributes": { - "id": "a1edd1e9-61b0-429c-a05a-31ac8d9d2b8b", - "name": "Vancouver", - "code": "CAVAN", - "state_abbr": "BC", - "city": "Vancouver", - "country_code": "CA", - "latitude": "49.287489751", - "longitude": "-123.094867064", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "4c6c85eb-79bc-4f7c-ad66-962a6ab3a506", - "type": "transport_event", - "attributes": { - "event": "container.transport.rail_loaded", - "created_at": "2022-10-21T19:29:49Z", - "voyage_number": null, - "timestamp": "2022-10-21T18:11:00Z", - "data_source": "shipping_line", - "location_locode": "CAVAN", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "93921adf-af96-4096-9797-2abea7e95e79", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "3be28cd9-165f-4af1-827a-902bef0147d0", - "type": "container" - } - }, - "vessel": { - "data": null - }, - "location": { - "data": { - "id": "a1edd1e9-61b0-429c-a05a-31ac8d9d2b8b", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - } - ] -} -``` - -## container.transport.rail_unloaded -```json -{ - "data": { - "id": "c64aa704-adad-4a45-a2f1-0173afedc598", - "type": "webhook_notification", - "attributes": { - "id": "c64aa704-adad-4a45-a2f1-0173afedc598", - "event": "container.transport.rail_unloaded", - "delivery_status": "pending", - "created_at": "2022-10-21T20:18:00Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "42a1cd6e-9891-401c-aa28-7e2dfcdf5895", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "99cd87c5-5bb2-49ba-96c8-a29f12159d82", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "0ee340ba-0f60-49cb-8ae1-551897887a52", - "type": "shipment", - "attributes": { - "created_at": "2022-09-28T14:19:08Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "OOLU2706578920", - "normalized_number": "2706578920", - "shipping_line_scac": "OOLU", - "shipping_line_name": "Orient Overseas Container Line", - "shipping_line_short_name": "OOCL", - "customer_name": "Schimmel-Beatty", - "port_of_lading_locode": "TWKHH", - "port_of_lading_name": "Kaohsiung", - "port_of_discharge_locode": "USLGB", - "port_of_discharge_name": "Long Beach", - "pod_vessel_name": "COSCO ENGLAND", - "pod_vessel_imo": "9516428", - "pod_voyage_number": "054E", - "destination_locode": "USEWI", - "destination_name": "Elwood", - "destination_timezone": "America/Chicago", - "destination_ata_at": "2022-10-21T17:43:00Z", - "destination_eta_at": "2022-10-18T09:36:00Z", - "pol_etd_at": null, - "pol_atd_at": "2022-09-27T02:05:00Z", - "pol_timezone": "Asia/Taipei", - "pod_eta_at": "2022-10-12T14:00:00Z", - "pod_original_eta_at": "2022-10-10T15:00:00Z", - "pod_ata_at": "2022-10-12T13:26:00Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": "2022-10-21T20:17:48Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "dbe43734-a6c0-40da-9dd8-955e3a536bcf", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "52f2038f-aecd-4329-b368-a339ae81e4b7", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "6a1aa48d-1493-48ff-91c9-48ef6997e63b", - "type": "terminal" - } - }, - "destination": { - "data": { - "id": "43b40e7b-e339-450e-8581-19e49cb32a61", - "type": "metro_area" - } - }, - "destination_terminal": { - "data": { - "id": "9d04a5cb-083b-4a76-86a8-e37fc3dd65b3", - "type": "rail_terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "7309bbe8-ec63-4740-979a-e4dc3047778a", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/7ffaff8a-8004-4c02-8acf-3d744708e0b4" - } - }, - { - "id": "7309bbe8-ec63-4740-979a-e4dc3047778a", - "type": "container", - "attributes": { - "number": "OOLU6213464", - "seal_number": null, - "created_at": "2022-09-28T14:19:08Z", - "ref_numbers": [ - - ], - "pod_arrived_at": "2022-10-12T13:26:00Z", - "pod_discharged_at": "2022-10-12T22:27:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - { - "status": "hold", - "name": "other", - "description": "ONDOCK" - }, - { - "status": "hold", - "name": "other", - "description": "CTF_CONTAINER_HOLD" - }, - { - "status": "hold", - "name": "freight", - "description": "FREIGHT_BL_HOLD" - } - ], - "available_for_pickup": false, - "equipment_type": "reefer", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": 56879, - "pod_full_out_at": "2022-10-14T16:21:00Z", - "empty_terminated_at": null, - "terminal_checked_at": "2022-10-14T20:50:59Z", - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "GROUNDED", - "pod_last_tracking_request_at": "2022-10-14T20:50:59Z", - "shipment_last_tracking_request_at": null, - "availability_known": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": "America/Chicago", - "empty_terminated_timezone": "America/Chicago" - }, - "relationships": { - "shipment": { - "data": { - "id": "0ee340ba-0f60-49cb-8ae1-551897887a52", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "6a1aa48d-1493-48ff-91c9-48ef6997e63b", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "9ae17caa-9c9c-47a2-8444-949b25a4edc5", - "type": "transport_event" - }, - { - "id": "3313e5fd-f7f1-4bab-b1ca-4eefe26fa533", - "type": "transport_event" - }, - { - "id": "bd5a09a1-f77f-4b8a-9f0e-38a2ad30a26c", - "type": "transport_event" - }, - { - "id": "969da0e3-eb68-4da2-96e8-3765fc22a03f", - "type": "transport_event" - }, - { - "id": "707b092c-7662-4dc1-93c8-e8893c797da2", - "type": "transport_event" - }, - { - "id": "0de0c1d0-9502-4474-ad6f-acd138f7775d", - "type": "transport_event" - }, - { - "id": "09d00e28-8e4a-4362-aade-63ed7cb70968", - "type": "transport_event" - }, - { - "id": "b23ff291-e087-45e0-8dbf-141062a47d20", - "type": "transport_event" - }, - { - "id": "095aa809-3e1e-424d-9cd7-92d49a8f0f43", - "type": "transport_event" - }, - { - "id": "ba55a78a-7391-46bd-ad85-558b9c1fcbb5", - "type": "transport_event" - }, - { - "id": "6ca9d38f-1a8f-411a-87d8-af3f174999a1", - "type": "transport_event" - }, - { - "id": "3cf739f9-198a-409e-a9a8-89724eeb4821", - "type": "transport_event" - }, - { - "id": "18294c8a-a619-4416-a112-83dfde0e757c", - "type": "transport_event" - }, - { - "id": "d56f9846-95b5-4acb-87de-3c93fda143f1", - "type": "transport_event" - }, - { - "id": "42a1cd6e-9891-401c-aa28-7e2dfcdf5895", - "type": "transport_event" - }, - { - "id": "dd9edd7b-1798-4663-88bc-a5ce07700534", - "type": "transport_event" - }, - { - "id": "66643a7e-3e34-4693-8a6f-1d85126f93d1", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "4853f3bf-2660-430e-89d0-f1516dcd48f7", - "type": "raw_event" - }, - { - "id": "a7ade087-4e66-4555-bd3e-140dfb08785a", - "type": "raw_event" - }, - { - "id": "bdd42309-84db-4337-a80a-2ff3e417e95e", - "type": "raw_event" - }, - { - "id": "8e6203e7-f431-4cfa-af04-5a7a5018e745", - "type": "raw_event" - }, - { - "id": "a42b14f8-5a08-41ff-a864-8f92bbad4bb0", - "type": "raw_event" - }, - { - "id": "e92b5727-669e-4490-bab8-66d6edbd1f31", - "type": "raw_event" - }, - { - "id": "a98b1ae3-26be-4585-b8f2-c439c726b723", - "type": "raw_event" - }, - { - "id": "6d4cb7b5-32d4-4051-937b-5e5ad1dbbbad", - "type": "raw_event" - }, - { - "id": "489961d2-2791-45e8-baf3-d975a1dc0a01", - "type": "raw_event" - }, - { - "id": "16e55312-dd6d-4def-b4c2-39fb0eeaa3ac", - "type": "raw_event" - }, - { - "id": "596312ac-becf-4ed4-bc69-074d889385dc", - "type": "raw_event" - }, - { - "id": "1e314cb2-b9e7-4be5-a63a-d185588c3b41", - "type": "raw_event" - }, - { - "id": "150fc217-0bd5-427e-b09c-b123ec97ce2c", - "type": "raw_event" - }, - { - "id": "7feacaf8-62b8-47f4-abb4-d5ee8fdda43b", - "type": "raw_event" - }, - { - "id": "9435c083-5af6-47f3-90cf-8a554f995e10", - "type": "raw_event" - }, - { - "id": "2f451b22-9770-456f-b7fc-b050e42e37ce", - "type": "raw_event" - }, - { - "id": "c34c12c2-6bc3-44eb-a321-56b0a07233e4", - "type": "raw_event" - }, - { - "id": "ffa3d8b7-153d-4e2d-95c8-608cebfa8e6e", - "type": "raw_event" - }, - { - "id": "ae0f633f-c7f7-426b-8f9e-6112efe54b54", - "type": "raw_event" - }, - { - "id": "a7adde60-cf9e-41b9-a372-0410a3cfb437", - "type": "raw_event" - }, - { - "id": "67976eb0-e900-4c67-8dc5-327651625db4", - "type": "raw_event" - }, - { - "id": "84658720-b52f-43c6-8d8a-02c3c848b67f", - "type": "raw_event" - }, - { - "id": "a376f1f4-016b-40ba-83b6-514cd27a7c4b", - "type": "raw_event" - }, - { - "id": "7995ed60-cc48-44f2-b733-46094ca9e1d4", - "type": "raw_event" - }, - { - "id": "e1b66db7-d5a4-4b6b-8386-b4fc3dd56154", - "type": "raw_event" - }, - { - "id": "8950c6cf-6150-43cd-bf80-64bf776493de", - "type": "raw_event" - }, - { - "id": "aec9df81-c580-4c60-b1f8-15ff2cc2a293", - "type": "raw_event" - }, - { - "id": "25135e6c-3417-4de3-a0ba-9ca30aa768ed", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "43b40e7b-e339-450e-8581-19e49cb32a61", - "type": "metro_area", - "attributes": { - "id": "43b40e7b-e339-450e-8581-19e49cb32a61", - "name": "Elwood", - "state_abbr": "IL", - "code": "USEWI", - "latitude": "41.4039201", - "longitude": "-88.1117242", - "country_code": "US", - "time_zone": "America/Chicago" - } - }, - { - "id": "9d04a5cb-083b-4a76-86a8-e37fc3dd65b3", - "type": "rail_terminal", - "attributes": { - "id": "9d04a5cb-083b-4a76-86a8-e37fc3dd65b3", - "nickname": "BNSF", - "name": "BNSF - Logistics Park Chicago (LPC) Intermodal Facility", - "city": "Elwood", - "firms_code": "H572" - }, - "relationships": { - "metro_area": { - "data": { - "id": "43b40e7b-e339-450e-8581-19e49cb32a61", - "type": "metro_area" - } - }, - "port": { - "data": null - } - } - }, - { - "id": "42a1cd6e-9891-401c-aa28-7e2dfcdf5895", - "type": "transport_event", - "attributes": { - "event": "container.transport.rail_unloaded", - "created_at": "2022-10-21T20:18:00Z", - "voyage_number": null, - "timestamp": "2022-10-21T18:32:00Z", - "data_source": "shipping_line", - "location_locode": "USEWI", - "timezone": "America/Chicago" - }, - "relationships": { - "shipment": { - "data": { - "id": "0ee340ba-0f60-49cb-8ae1-551897887a52", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "7309bbe8-ec63-4740-979a-e4dc3047778a", - "type": "container" - } - }, - "vessel": { - "data": null - }, - "location": { - "data": { - "id": "43b40e7b-e339-450e-8581-19e49cb32a61", - "type": "metro_area" - } - }, - "terminal": { - "data": { - "id": "9d04a5cb-083b-4a76-86a8-e37fc3dd65b3", - "type": "rail_terminal" - } - } - } - } - ] -} -``` - -## container.transport.transshipment_arrived -```json -{ - "data": { - "id": "8711bde5-8172-414b-b418-822c01ac8702", - "type": "webhook_notification", - "attributes": { - "id": "8711bde5-8172-414b-b418-822c01ac8702", - "event": "container.transport.transshipment_arrived", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:16:41Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "a784ad6d-55e5-40ec-83d2-06bc12b4cbe6", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "441a3525-f2d7-4484-a46b-d89727cd3de6", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "9a86d383-af2e-4aaf-84ff-d868c4145de6", - "type": "shipment", - "attributes": { - "created_at": "2022-10-21T20:15:38Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "914595688", - "normalized_number": "914595688", - "shipping_line_scac": "SEAU", - "shipping_line_name": "Sealand Americas", - "shipping_line_short_name": "SeaLand Americas", - "customer_name": "Runolfsson-Fisher", - "port_of_lading_locode": "CLARI", - "port_of_lading_name": "Arica", - "port_of_discharge_locode": "USNYC", - "port_of_discharge_name": "New York / New Jersey", - "pod_vessel_name": "NORTHERN PRIORITY", - "pod_vessel_imo": "9450313", - "pod_voyage_number": "242N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2022-09-28T19:51:00Z", - "pol_timezone": "America/Santiago", - "pod_eta_at": "2022-10-27T12:00:00Z", - "pod_original_eta_at": "2022-10-27T12:00:00Z", - "pod_ata_at": null, - "pod_timezone": "America/New_York", - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "d49cd76a-8ee2-4b1e-90f7-ea75c008bbfb", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "6dfceea1-86b1-426b-89e5-a20a4f134b58", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "c021339d-7145-429f-b713-9381c6874410", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "e68fc5f3-d058-47dd-935f-3b511b97f963", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/2de26519-3fb0-4748-b5ea-fca2b68dcab1" - } - }, - { - "id": "e68fc5f3-d058-47dd-935f-3b511b97f963", - "type": "container", - "attributes": { - "number": "MNBU4188482", - "seal_number": null, - "created_at": "2022-10-21T20:15:38Z", - "ref_numbers": [ - - ], - "pod_arrived_at": null, - "pod_discharged_at": null, - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": false, - "equipment_type": "reefer", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": null, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "pod_last_tracking_request_at": null, - "shipment_last_tracking_request_at": null, - "availability_known": false, - "pod_timezone": "America/New_York", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/New_York" - }, - "relationships": { - "shipment": { - "data": { - "id": "9a86d383-af2e-4aaf-84ff-d868c4145de6", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "c021339d-7145-429f-b713-9381c6874410", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "48460e5e-109f-441e-85e5-f2dafdc0b580", - "type": "transport_event" - }, - { - "id": "a0b4028a-cd0d-4cee-81ec-fbcad0bbd6e4", - "type": "transport_event" - }, - { - "id": "cb7e6907-6f19-4993-9537-e5639f012855", - "type": "transport_event" - }, - { - "id": "28dce1a7-be9c-420b-9204-cb5ba1a8d9f5", - "type": "transport_event" - }, - { - "id": "a784ad6d-55e5-40ec-83d2-06bc12b4cbe6", - "type": "transport_event" - }, - { - "id": "8f90ebb0-e7ac-4dc3-8289-8d8d707cb599", - "type": "transport_event" - }, - { - "id": "e759a7c1-4698-43fa-8655-9e587cd543a0", - "type": "transport_event" - }, - { - "id": "3cc0cafb-1e9b-4cfa-8b36-bac8d0637bbc", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "686817d9-a55a-4ddb-a6c3-dbddc9da2c8d", - "type": "raw_event" - }, - { - "id": "a6fad72d-f691-4b39-bec9-1ad74f3ba00c", - "type": "raw_event" - }, - { - "id": "1debea05-bdef-40ed-afb1-85df593befb2", - "type": "raw_event" - }, - { - "id": "cb838330-54fe-4ae3-94d2-d6494a4553b4", - "type": "raw_event" - }, - { - "id": "d87dbb59-9b0c-4232-a04c-0b6b1400f865", - "type": "raw_event" - }, - { - "id": "d0d79af0-7bf0-4a87-b14b-219ae8d46a70", - "type": "raw_event" - }, - { - "id": "abe79851-1346-45a2-8134-60c2c20c82ad", - "type": "raw_event" - }, - { - "id": "eb2e1b88-570b-449b-a4c8-ec90f75221b0", - "type": "raw_event" - }, - { - "id": "1bbf2111-7125-4b56-877c-13eca65eec23", - "type": "raw_event" - }, - { - "id": "9c729bea-2e19-4bca-b6a4-a9112562ffd6", - "type": "raw_event" - }, - { - "id": "b75f1d15-375a-41a6-91cb-0055a7eb681e", - "type": "raw_event" - }, - { - "id": "e6fa1c43-f6a4-459a-840f-312e24257832", - "type": "raw_event" - }, - { - "id": "c4b19e31-309c-4454-9711-3adf028fca5e", - "type": "raw_event" - }, - { - "id": "150a808a-a9c4-4807-b3e6-21f0e74491ef", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "b8047d9a-62de-4f10-b0d9-abc6ff025b67", - "type": "port", - "attributes": { - "id": "b8047d9a-62de-4f10-b0d9-abc6ff025b67", - "name": "Balboa", - "code": "PABLB", - "state_abbr": null, - "city": null, - "country_code": "PA", - "latitude": "8.958933348", - "longitude": "-79.565420224", - "time_zone": "America/Panama" - } - }, - { - "id": "50ce561a-577f-46d1-af67-b236b847f51d", - "type": "vessel", - "attributes": { - "name": "MERIDIAN", - "imo": "7002605", - "mmsi": "218415000", - "latitude": -78.30435842851921, - "longitude": 25.471353799804547, - "nautical_speed_knots": 100, - "navigational_heading_degrees": 1, - "position_timestamp": "2023-06-05T19:46:18Z" - } - }, - { - "id": "a784ad6d-55e5-40ec-83d2-06bc12b4cbe6", - "type": "transport_event", - "attributes": { - "event": "container.transport.transshipment_arrived", - "created_at": "2022-10-21T20:15:38Z", - "voyage_number": "239N", - "timestamp": "2022-10-11T13:01:00Z", - "data_source": "shipping_line", - "location_locode": "PABLB", - "timezone": "America/Panama" - }, - "relationships": { - "shipment": { - "data": { - "id": "9a86d383-af2e-4aaf-84ff-d868c4145de6", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "e68fc5f3-d058-47dd-935f-3b511b97f963", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "50ce561a-577f-46d1-af67-b236b847f51d", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "b8047d9a-62de-4f10-b0d9-abc6ff025b67", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - } - ] -} -``` - -## container.transport.transshipment_departed -```json -{ - "data": { - "id": "93006fa9-7ff4-49c3-ba69-4266aac3b54b", - "type": "webhook_notification", - "attributes": { - "id": "93006fa9-7ff4-49c3-ba69-4266aac3b54b", - "event": "container.transport.transshipment_departed", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:16:41Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "e82e8530-1cf5-4680-9120-a737dde69083", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "1e6cd9f5-cb7e-43be-8a7e-99ebcd08106c", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "ccdf4809-0965-49c1-9dfb-9f4b250f3afd", - "type": "shipment", - "attributes": { - "created_at": "2022-10-21T20:15:38Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "914595688", - "normalized_number": "914595688", - "shipping_line_scac": "SEAU", - "shipping_line_name": "Sealand Americas", - "shipping_line_short_name": "SeaLand Americas", - "customer_name": "Quigley, Romaguera and McDermott", - "port_of_lading_locode": "CLARI", - "port_of_lading_name": "Arica", - "port_of_discharge_locode": "USNYC", - "port_of_discharge_name": "New York / New Jersey", - "pod_vessel_name": "NORTHERN PRIORITY", - "pod_vessel_imo": "9450313", - "pod_voyage_number": "242N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2022-09-28T19:51:00Z", - "pol_timezone": "America/Santiago", - "pod_eta_at": "2022-10-27T12:00:00Z", - "pod_original_eta_at": "2022-10-27T12:00:00Z", - "pod_ata_at": null, - "pod_timezone": "America/New_York", - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "6c07b6aa-7f5c-469a-8122-9509251e87c3", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "9f951b96-5871-442d-a002-5e7b2b1bbb08", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "04f1344e-c5a2-43f1-9a20-7bf94258720b", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "a7a9790d-ec68-4afd-9063-a9cba0ec0cd9", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/2de26519-3fb0-4748-b5ea-fca2b68dcab1" - } - }, - { - "id": "a7a9790d-ec68-4afd-9063-a9cba0ec0cd9", - "type": "container", - "attributes": { - "number": "MNBU4188482", - "seal_number": null, - "created_at": "2022-10-21T20:15:38Z", - "ref_numbers": [ - - ], - "pod_arrived_at": null, - "pod_discharged_at": null, - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": false, - "equipment_type": "reefer", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": null, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "pod_last_tracking_request_at": null, - "shipment_last_tracking_request_at": null, - "availability_known": false, - "pod_timezone": "America/New_York", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/New_York" - }, - "relationships": { - "shipment": { - "data": { - "id": "ccdf4809-0965-49c1-9dfb-9f4b250f3afd", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "04f1344e-c5a2-43f1-9a20-7bf94258720b", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "ca941257-104d-4556-881b-59a03ff27fa4", - "type": "transport_event" - }, - { - "id": "d17dd416-ee98-4a9b-9946-e053a246b79a", - "type": "transport_event" - }, - { - "id": "57aee521-0be4-4d8a-9b59-e90773371e46", - "type": "transport_event" - }, - { - "id": "8a3e98a1-ce78-4782-9173-55c2a457101e", - "type": "transport_event" - }, - { - "id": "4307eb90-b617-4b8e-8c5c-e9fa9f3b94d1", - "type": "transport_event" - }, - { - "id": "f4b2c3d6-1d6b-465a-ba59-c2b228683850", - "type": "transport_event" - }, - { - "id": "bdd4624d-5e59-4207-a03f-ee8ae00522aa", - "type": "transport_event" - }, - { - "id": "e82e8530-1cf5-4680-9120-a737dde69083", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "d87a8883-6ebc-402a-bf58-75837200b24e", - "type": "raw_event" - }, - { - "id": "08637455-91e6-4cb4-b68c-da7da5ce9eb0", - "type": "raw_event" - }, - { - "id": "bf9ea8d3-982e-4be6-aaaf-91a96c5081e2", - "type": "raw_event" - }, - { - "id": "b33f0ce7-0b01-4760-a61d-8792d1a5075d", - "type": "raw_event" - }, - { - "id": "dacc8b9e-95d9-4894-8ab2-a3172726b711", - "type": "raw_event" - }, - { - "id": "d1875e34-e862-44bf-8011-8f8e25d40222", - "type": "raw_event" - }, - { - "id": "9e5bf153-aa86-461e-bb7f-f75a44a4375c", - "type": "raw_event" - }, - { - "id": "c6fc7390-c634-44f2-b2a7-02c1397146f4", - "type": "raw_event" - }, - { - "id": "67cbee01-9731-4df4-b763-15ef0996abd8", - "type": "raw_event" - }, - { - "id": "dcc6745d-cb84-4575-9ecb-ea8017017289", - "type": "raw_event" - }, - { - "id": "798201b5-62f4-4d0b-951c-20d51960029b", - "type": "raw_event" - }, - { - "id": "a54fdf85-e96c-4bad-a993-f7ee0e857b9e", - "type": "raw_event" - }, - { - "id": "92b43e47-7af6-4317-bd1c-ee1dad0b6ab1", - "type": "raw_event" - }, - { - "id": "21c8fa3d-5702-4240-b9af-277d3853b441", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "083870c7-a84c-4258-a033-73550322a336", - "type": "port", - "attributes": { - "id": "083870c7-a84c-4258-a033-73550322a336", - "name": "Manzanillo", - "code": "PAMIT", - "state_abbr": null, - "city": null, - "country_code": "PA", - "latitude": "9.362360956", - "longitude": "-79.882591837", - "time_zone": "America/Panama" - } - }, - { - "id": "80750832-9ecf-4dae-b290-3263460fcbb1", - "type": "vessel", - "attributes": { - "name": "NORTHERN PRIORITY", - "imo": "9450313", - "mmsi": "636091832", - "latitude": -78.30435842851921, - "longitude": 25.471353799804547, - "nautical_speed_knots": 100, - "navigational_heading_degrees": 1, - "position_timestamp": "2023-06-05T19:46:18Z" - } - }, - { - "id": "e82e8530-1cf5-4680-9120-a737dde69083", - "type": "transport_event", - "attributes": { - "event": "container.transport.transshipment_departed", - "created_at": "2022-10-21T20:15:39Z", - "voyage_number": "242N", - "timestamp": "2022-10-20T06:01:00Z", - "data_source": "shipping_line", - "location_locode": "PAMIT", - "timezone": "America/Panama" - }, - "relationships": { - "shipment": { - "data": { - "id": "ccdf4809-0965-49c1-9dfb-9f4b250f3afd", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "a7a9790d-ec68-4afd-9063-a9cba0ec0cd9", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "80750832-9ecf-4dae-b290-3263460fcbb1", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "083870c7-a84c-4258-a033-73550322a336", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - } - ] -} -``` - -## container.transport.transshipment_discharged -```json -{ - "data": { - "id": "a50e58c5-60eb-453e-b797-de590914d9c6", - "type": "webhook_notification", - "attributes": { - "id": "a50e58c5-60eb-453e-b797-de590914d9c6", - "event": "container.transport.transshipment_discharged", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:17:33Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "2051adf7-b0d4-4f8c-9bb4-5cee4987d7a1", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "d1b29b83-d7f3-4a4a-be86-b3b2e359a021", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "eb2571f2-b189-4d84-ac49-6a606e1f3ce8", - "type": "shipment", - "attributes": { - "created_at": "2022-10-13T06:24:29Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "6345849250", - "normalized_number": "6345849250", - "shipping_line_scac": "COSU", - "shipping_line_name": "COSCO", - "shipping_line_short_name": "COSCO", - "customer_name": "Bruen, Orn and Ruecker", - "port_of_lading_locode": "NOBVK", - "port_of_lading_name": "Brevik", - "port_of_discharge_locode": "IDJKT", - "port_of_discharge_name": "Jakarta, Java", - "pod_vessel_name": "CTP MAKASSAR", - "pod_vessel_imo": "9181742", - "pod_voyage_number": "446N", - "destination_locode": null, - "destination_name": "Jakarta,Indonesia", - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": "2022-12-05T14:00:00Z", - "pol_etd_at": "2022-10-18T10:00:00Z", - "pol_atd_at": "2022-10-18T22:12:00Z", - "pol_timezone": "Europe/Oslo", - "pod_eta_at": "2022-12-05T12:00:00Z", - "pod_original_eta_at": "2022-11-28T12:00:00Z", - "pod_ata_at": null, - "pod_timezone": "Asia/Jakarta", - "line_tracking_last_attempted_at": "2022-10-21T20:17:28Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "d4975962-efc1-47bf-9f94-b32b20645678", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "c1a4b096-90fb-4c48-b797-1646af7a184d", - "type": "port" - } - }, - "pod_terminal": { - "data": null - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "50959d84-85d4-47a0-a822-496383968eb5", - "type": "container" - }, - { - "id": "79f2bc77-8490-43c6-9938-b6ee1723388e", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/743450bd-e1e6-4c7c-8e82-5987a5aa578b" - } - }, - { - "id": "50959d84-85d4-47a0-a822-496383968eb5", - "type": "container", - "attributes": { - "number": "OOLU1927772", - "seal_number": "0167667", - "created_at": "2022-10-13T06:24:29Z", - "ref_numbers": [ - - ], - "pod_arrived_at": null, - "pod_discharged_at": null, - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 20, - "equipment_height": "standard", - "weight_in_lbs": 59525, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "pod_last_tracking_request_at": null, - "shipment_last_tracking_request_at": null, - "availability_known": false, - "pod_timezone": "Asia/Jakarta", - "final_destination_timezone": null, - "empty_terminated_timezone": "Asia/Jakarta" - }, - "relationships": { - "shipment": { - "data": { - "id": "eb2571f2-b189-4d84-ac49-6a606e1f3ce8", - "type": "shipment" - } - }, - "pod_terminal": { - "data": null - }, - "transport_events": { - "data": [ - { - "id": "478eaae8-e93e-45d6-b7ff-868a9eeeae20", - "type": "transport_event" - }, - { - "id": "7c471ed0-98b5-4ab3-9e8b-732a5a54c65b", - "type": "transport_event" - }, - { - "id": "21c59959-0fcb-44cf-ba77-3a117c119c9c", - "type": "transport_event" - }, - { - "id": "55a4bbb5-cf7f-48e1-82fb-faa07be87580", - "type": "transport_event" - }, - { - "id": "2051adf7-b0d4-4f8c-9bb4-5cee4987d7a1", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "2dc91aa8-b9ae-43e3-bd7a-6092630d3c2e", - "type": "raw_event" - }, - { - "id": "681a2cab-337f-41aa-af4f-52b7c1a11e47", - "type": "raw_event" - }, - { - "id": "07452b4d-dead-4db6-973b-51cce89e9528", - "type": "raw_event" - }, - { - "id": "a8bca88f-61f6-4747-8e6c-a8e0b832733d", - "type": "raw_event" - }, - { - "id": "847e6c3f-bdde-47bd-b0d2-272381fdf327", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "2a7f2058-e408-4259-baa9-ecf2c60ff275", - "type": "port", - "attributes": { - "id": "2a7f2058-e408-4259-baa9-ecf2c60ff275", - "name": "Rotterdam", - "code": "NLRTM", - "state_abbr": null, - "city": null, - "country_code": "NL", - "latitude": "51.956693922", - "longitude": "4.063456434", - "time_zone": "Europe/Amsterdam" - } - }, - { - "id": "19f128eb-9be0-45de-8008-7d66dc7b0091", - "type": "vessel", - "attributes": { - "name": "ELBSPRING", - "imo": "9412529", - "mmsi": "305575000", - "latitude": -78.30435842851921, - "longitude": 25.471353799804547, - "nautical_speed_knots": 100, - "navigational_heading_degrees": 1, - "position_timestamp": "2023-06-05T19:46:18Z" - } - }, - { - "id": "2051adf7-b0d4-4f8c-9bb4-5cee4987d7a1", - "type": "transport_event", - "attributes": { - "event": "container.transport.transshipment_discharged", - "created_at": "2022-10-21T20:17:33Z", - "voyage_number": "50", - "timestamp": "2022-10-21T16:00:00Z", - "data_source": "shipping_line", - "location_locode": "NLRTM", - "timezone": "Europe/Amsterdam" - }, - "relationships": { - "shipment": { - "data": { - "id": "eb2571f2-b189-4d84-ac49-6a606e1f3ce8", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "50959d84-85d4-47a0-a822-496383968eb5", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "19f128eb-9be0-45de-8008-7d66dc7b0091", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "2a7f2058-e408-4259-baa9-ecf2c60ff275", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - } - ] -} -``` - -## container.transport.transshipment_loaded -```json -{ - "data": { - "id": "e41c559a-3179-4e17-b739-d2458ff972a3", - "type": "webhook_notification", - "attributes": { - "id": "e41c559a-3179-4e17-b739-d2458ff972a3", - "event": "container.transport.transshipment_loaded", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:16:41Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "abd25313-fc0f-4ad6-a9a0-5afdfe2116e2", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "f6e45855-97de-4c13-ba6c-ae2ee42f9d70", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "1e52ad99-2d0e-41ef-87ad-ea3572e2899e", - "type": "shipment", - "attributes": { - "created_at": "2022-10-21T20:15:38Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "914595688", - "normalized_number": "914595688", - "shipping_line_scac": "SEAU", - "shipping_line_name": "Sealand Americas", - "shipping_line_short_name": "SeaLand Americas", - "customer_name": "Kozey, Ortiz and Legros", - "port_of_lading_locode": "CLARI", - "port_of_lading_name": "Arica", - "port_of_discharge_locode": "USNYC", - "port_of_discharge_name": "New York / New Jersey", - "pod_vessel_name": "NORTHERN PRIORITY", - "pod_vessel_imo": "9450313", - "pod_voyage_number": "242N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2022-09-28T19:51:00Z", - "pol_timezone": "America/Santiago", - "pod_eta_at": "2022-10-27T12:00:00Z", - "pod_original_eta_at": "2022-10-27T12:00:00Z", - "pod_ata_at": null, - "pod_timezone": "America/New_York", - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "5a56e8c3-bac0-47da-99c6-cd83ab428a80", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "990a5038-5273-4fe8-9a9f-4f1de2bcd418", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "62c6c1d7-757b-4cde-b189-9c6898d69be3", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "60998326-fb38-43c7-af7f-a0cc45825152", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/2de26519-3fb0-4748-b5ea-fca2b68dcab1" - } - }, - { - "id": "60998326-fb38-43c7-af7f-a0cc45825152", - "type": "container", - "attributes": { - "number": "MNBU4188482", - "seal_number": null, - "created_at": "2022-10-21T20:15:38Z", - "ref_numbers": [ - - ], - "pod_arrived_at": null, - "pod_discharged_at": null, - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": false, - "equipment_type": "reefer", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": null, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "pod_last_tracking_request_at": null, - "shipment_last_tracking_request_at": null, - "availability_known": false, - "pod_timezone": "America/New_York", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/New_York" - }, - "relationships": { - "shipment": { - "data": { - "id": "1e52ad99-2d0e-41ef-87ad-ea3572e2899e", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "62c6c1d7-757b-4cde-b189-9c6898d69be3", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "0923b9f0-23aa-48b7-ba1c-a9069a6b18fe", - "type": "transport_event" - }, - { - "id": "b7926c0c-6d32-4952-8f45-93cf8b7be737", - "type": "transport_event" - }, - { - "id": "28c51b2e-22fb-4f92-a065-92cd5ec9a189", - "type": "transport_event" - }, - { - "id": "d21d539e-11fd-42c6-a9df-7b2d72f1efbe", - "type": "transport_event" - }, - { - "id": "3f8a6bc0-3fda-4a82-941a-5d75bf5f1dea", - "type": "transport_event" - }, - { - "id": "c42d173f-cebc-4caf-8c7d-ac11374aa3fc", - "type": "transport_event" - }, - { - "id": "abd25313-fc0f-4ad6-a9a0-5afdfe2116e2", - "type": "transport_event" - }, - { - "id": "ed8ee17c-276c-428a-a4c5-321ceb735293", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "742004b2-db59-4d68-8e94-35523d02b911", - "type": "raw_event" - }, - { - "id": "412d71b3-6314-4eb8-a2f7-9e582c5ec1b9", - "type": "raw_event" - }, - { - "id": "a03d1986-0889-4ba1-83a1-473209f026f5", - "type": "raw_event" - }, - { - "id": "f721632d-257a-4001-b8ab-9a66fff06bb8", - "type": "raw_event" - }, - { - "id": "fc0b6306-dcce-497f-b5e8-d4b445bc9ce7", - "type": "raw_event" - }, - { - "id": "de6dd640-9f81-4b8c-ba1a-95da3da1d415", - "type": "raw_event" - }, - { - "id": "7b60fd0f-d53e-47fd-a8df-d206df12ae30", - "type": "raw_event" - }, - { - "id": "1858d515-fa61-4a6c-979d-718b511304ff", - "type": "raw_event" - }, - { - "id": "28e25e0a-d57c-4294-8a96-b5f8e41777fa", - "type": "raw_event" - }, - { - "id": "4c003cd5-c1c6-4dbd-9a6a-8c7b98b0c176", - "type": "raw_event" - }, - { - "id": "b77b40c6-6df3-4324-ba58-6f11cfa430a7", - "type": "raw_event" - }, - { - "id": "6aa41832-c607-40fb-924b-8f7017cbbb1b", - "type": "raw_event" - }, - { - "id": "da1617cf-1557-4766-bd1c-6c60bc32db87", - "type": "raw_event" - }, - { - "id": "26dcb356-a6e4-4255-bc29-a1b074cfb8f0", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "928f91f5-74d0-4a65-b5cf-995a4a026770", - "type": "port", - "attributes": { - "id": "928f91f5-74d0-4a65-b5cf-995a4a026770", - "name": "Manzanillo", - "code": "PAMIT", - "state_abbr": null, - "city": null, - "country_code": "PA", - "latitude": "9.362360956", - "longitude": "-79.882591837", - "time_zone": "America/Panama" - } - }, - { - "id": "2327b55c-4abb-410d-b97f-dcddf4734f89", - "type": "vessel", - "attributes": { - "name": "NORTHERN PRIORITY", - "imo": "9450313", - "mmsi": "636091832", - "latitude": -78.30435842851921, - "longitude": 25.471353799804547, - "nautical_speed_knots": 100, - "navigational_heading_degrees": 1, - "position_timestamp": "2023-06-05T19:46:18Z" - } - }, - { - "id": "abd25313-fc0f-4ad6-a9a0-5afdfe2116e2", - "type": "transport_event", - "attributes": { - "event": "container.transport.transshipment_loaded", - "created_at": "2022-10-21T20:15:38Z", - "voyage_number": "242N", - "timestamp": "2022-10-19T18:09:00Z", - "data_source": "shipping_line", - "location_locode": "PAMIT", - "timezone": "America/Panama" - }, - "relationships": { - "shipment": { - "data": { - "id": "1e52ad99-2d0e-41ef-87ad-ea3572e2899e", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "60998326-fb38-43c7-af7f-a0cc45825152", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "2327b55c-4abb-410d-b97f-dcddf4734f89", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "928f91f5-74d0-4a65-b5cf-995a4a026770", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - } - ] -} -``` - -## container.transport.vessel_arrived -```json -{ - "data": { - "id": "be283e7f-6d95-4d87-b8cc-e4a88a0738ac", - "type": "webhook_notification", - "attributes": { - "id": "be283e7f-6d95-4d87-b8cc-e4a88a0738ac", - "event": "container.transport.vessel_arrived", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:15:39Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "3e814da7-53fe-4397-9776-c97e248eda91", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "361830fa-b087-47f2-94b0-06af1ac5d2a8", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "e55680a0-3378-428e-9783-7d72f9c3fc7f", - "type": "shipment", - "attributes": { - "created_at": "2022-09-09T12:05:57Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "ZIMULEH9024455", - "normalized_number": "ZIMULEH9024455", - "shipping_line_scac": "ZIMU", - "shipping_line_name": "Zim American Integrated Shipping Services", - "shipping_line_short_name": "Zim Line", - "customer_name": "Denesik, Senger and Feil", - "port_of_lading_locode": "FRLEH", - "port_of_lading_name": "Le Havre", - "port_of_discharge_locode": "INNSA", - "port_of_discharge_name": "Nhava Sheva", - "pod_vessel_name": "TONGALA", - "pod_vessel_imo": "9278105", - "pod_voyage_number": "132/E", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": "2022-09-28T22:00:00Z", - "pol_atd_at": "2022-09-28T13:40:00Z", - "pol_timezone": "Europe/Paris", - "pod_eta_at": "2022-10-20T18:30:00Z", - "pod_original_eta_at": "2022-10-13T18:30:00Z", - "pod_ata_at": "2022-10-21T15:53:00Z", - "pod_timezone": "Asia/Calcutta", - "line_tracking_last_attempted_at": "2022-10-21T19:32:09Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "b9e91f35-d472-48a8-912d-dbb1f85fe38e", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "be2b403c-ebaa-4125-aebd-3471ec765edf", - "type": "port" - } - }, - "pod_terminal": { - "data": null - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "5c3e90ed-5c8c-4c01-a0ce-ea544a2a9f6d", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/ae598754-a193-4a1f-9a4e-483d78b48d8c" - } - }, - { - "id": "5c3e90ed-5c8c-4c01-a0ce-ea544a2a9f6d", - "type": "container", - "attributes": { - "number": "CAIU3758064", - "seal_number": null, - "created_at": "2022-09-09T12:05:58Z", - "ref_numbers": [ - - ], - "pod_arrived_at": "2022-10-21T15:53:00Z", - "pod_discharged_at": null, - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 20, - "equipment_height": "standard", - "weight_in_lbs": null, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "pod_last_tracking_request_at": null, - "shipment_last_tracking_request_at": null, - "availability_known": false, - "pod_timezone": "Asia/Calcutta", - "final_destination_timezone": null, - "empty_terminated_timezone": "Asia/Calcutta" - }, - "relationships": { - "shipment": { - "data": { - "id": "e55680a0-3378-428e-9783-7d72f9c3fc7f", - "type": "shipment" - } - }, - "pod_terminal": { - "data": null - }, - "transport_events": { - "data": [ - { - "id": "2f50d744-8c94-4d2c-9a84-857b7871010f", - "type": "transport_event" - }, - { - "id": "58fa4da4-e265-45f2-aa47-63596834d094", - "type": "transport_event" - }, - { - "id": "3e814da7-53fe-4397-9776-c97e248eda91", - "type": "transport_event" - }, - { - "id": "218d96b8-01e8-4349-852a-b03e05182d56", - "type": "transport_event" - }, - { - "id": "1a74d4d0-d24c-47c9-8818-59ae75562c8f", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "e96fa9f3-b83b-4340-9853-ca7f793c0ee9", - "type": "raw_event" - }, - { - "id": "9d652d24-2753-4b36-b267-c456dee0ce7f", - "type": "raw_event" - }, - { - "id": "b06b1ad9-c029-435c-99a0-e2dc6e7121c3", - "type": "raw_event" - }, - { - "id": "89aa1a6d-3bb4-477e-a4e3-b8ee9be02b35", - "type": "raw_event" - }, - { - "id": "c4b65cc1-7def-46f7-b9e4-35a22d1a58a5", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "be2b403c-ebaa-4125-aebd-3471ec765edf", - "type": "port", - "attributes": { - "id": "be2b403c-ebaa-4125-aebd-3471ec765edf", - "name": "Nhava Sheva", - "code": "INNSA", - "state_abbr": null, - "city": null, - "country_code": "IN", - "latitude": "18.95580615", - "longitude": "72.951204698", - "time_zone": "Asia/Calcutta" - } - }, - { - "id": "e55637e2-3fd7-405e-b987-401c1c880905", - "type": "vessel", - "attributes": { - "name": "TONGALA", - "imo": "9278105", - "mmsi": "636013644", - "latitude": -78.30435842851921, - "longitude": 25.471353799804547, - "nautical_speed_knots": 100, - "navigational_heading_degrees": 1, - "position_timestamp": "2023-06-05T19:46:18Z" - } - }, - { - "id": "3e814da7-53fe-4397-9776-c97e248eda91", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_arrived", - "created_at": "2022-10-21T20:15:39Z", - "voyage_number": "132/E", - "timestamp": "2022-10-21T15:53:00Z", - "data_source": "shipping_line", - "location_locode": "INNSA", - "timezone": "Asia/Calcutta" - }, - "relationships": { - "shipment": { - "data": { - "id": "e55680a0-3378-428e-9783-7d72f9c3fc7f", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "5c3e90ed-5c8c-4c01-a0ce-ea544a2a9f6d", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "e55637e2-3fd7-405e-b987-401c1c880905", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "be2b403c-ebaa-4125-aebd-3471ec765edf", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - } - ] -} -``` - -## container.transport.vessel_berthed -```json -{ - "data": { - "id": "4c2bee7b-5929-4a8d-baa4-b8f8580597be", - "type": "webhook_notification", - "attributes": { - "id": "4c2bee7b-5929-4a8d-baa4-b8f8580597be", - "event": "container.transport.vessel_berthed", - "delivery_status": "succeeded", - "created_at": "2022-10-21T18:52:26Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "6242e7d4-eeea-40ec-af56-fc0a71e2a36b", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "9ed8cc42-5b0e-4ffc-96a6-335e96cdfcba", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "f028a4c5-4c0b-4894-b4b2-4919d266737e", - "type": "shipment", - "attributes": { - "created_at": "2022-09-22T08:25:55Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "HDMUXMNM78424800", - "normalized_number": "XMNM78424800", - "shipping_line_scac": "HDMU", - "shipping_line_name": "Hyundai Merchant Marine", - "shipping_line_short_name": "Hyundai", - "customer_name": "Kuvalis-Paucek", - "port_of_lading_locode": "CNXMN", - "port_of_lading_name": "Xiamen", - "port_of_discharge_locode": "USLAX", - "port_of_discharge_name": "Los Angeles", - "pod_vessel_name": "YM UNANIMITY", - "pod_vessel_imo": "9462718", - "pod_voyage_number": "0063E", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": "2022-09-27T22:00:00Z", - "pol_atd_at": "2022-09-28T02:30:00Z", - "pol_timezone": "Asia/Shanghai", - "pod_eta_at": "2022-10-17T13:00:00Z", - "pod_original_eta_at": "2022-10-15T15:00:00Z", - "pod_ata_at": "2022-10-18T17:35:17Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": "2022-10-21T18:52:23Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "5d392f9e-e3af-41d2-b7cf-7c8e48b5277d", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "214edbe5-f438-4492-bb18-3cd053c92cc7", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "b76896e1-fb93-4ebe-bbb2-d9e7eefc7553", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "82052525-07c1-4c45-9a09-e7633cbab373", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/a065301b-0205-496a-a317-ef0abd2ae2e3" - } - }, - { - "id": "82052525-07c1-4c45-9a09-e7633cbab373", - "type": "container", - "attributes": { - "number": "GAOU6337366", - "seal_number": "", - "created_at": "2022-09-22T08:25:55Z", - "ref_numbers": [ - - ], - "pod_arrived_at": "2022-10-17T13:00:00Z", - "pod_discharged_at": "2022-10-18T17:21:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": true, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": 0, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": "2022-10-21T19:44:09Z", - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": "2022-10-24T07:00:00Z", - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "In Yard
(Decked)", - "pod_last_tracking_request_at": "2022-10-21T19:44:09Z", - "shipment_last_tracking_request_at": null, - "availability_known": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "f028a4c5-4c0b-4894-b4b2-4919d266737e", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "b76896e1-fb93-4ebe-bbb2-d9e7eefc7553", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "82abbf20-17da-43ce-b62e-a94f0dbc51ad", - "type": "transport_event" - }, - { - "id": "75641949-c9b1-4726-b9dd-58577f3c0709", - "type": "transport_event" - }, - { - "id": "89deda0c-ff71-4e87-988e-cdb96f1af4b2", - "type": "transport_event" - }, - { - "id": "2553a3b5-e41f-4e22-9798-1aab7f275d35", - "type": "transport_event" - }, - { - "id": "6242e7d4-eeea-40ec-af56-fc0a71e2a36b", - "type": "transport_event" - }, - { - "id": "04fb0c48-fa3a-42e2-af35-0bec1d09f141", - "type": "transport_event" - }, - { - "id": "c45fe70b-83bf-46dd-ba5a-db03002ba349", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "ccb3e789-3902-469f-bbab-96a1ede50dd8", - "type": "raw_event" - }, - { - "id": "231b3fbe-6d33-4386-8258-1356ee9b9518", - "type": "raw_event" - }, - { - "id": "5d83c231-c711-42a0-abfc-6fedb259381f", - "type": "raw_event" - }, - { - "id": "a23c7de8-2ae0-4ca6-9c54-97d905d11a58", - "type": "raw_event" - }, - { - "id": "9e6700a1-15cd-4010-b8fc-799de9e3b1a5", - "type": "raw_event" - }, - { - "id": "8e50b041-1f9c-40bf-ab78-588e69bc4311", - "type": "raw_event" - }, - { - "id": "8027c3fc-c55c-4cb0-98de-ecfb2c8aa0ad", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "214edbe5-f438-4492-bb18-3cd053c92cc7", - "type": "port", - "attributes": { - "id": "214edbe5-f438-4492-bb18-3cd053c92cc7", - "name": "Los Angeles", - "code": "USLAX", - "state_abbr": "CA", - "city": "Los Angeles", - "country_code": "US", - "latitude": "33.728193631", - "longitude": "-118.255820307", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "b76896e1-fb93-4ebe-bbb2-d9e7eefc7553", - "type": "terminal", - "attributes": { - "id": "b76896e1-fb93-4ebe-bbb2-d9e7eefc7553", - "nickname": "WBCT", - "name": "West Basin Container Terminal", - "firms_code": "Y773", - "smdg_code": null, - "bic_facility_code": null, - "provided_data": { - "pickup_lfd": false, - "pickup_lfd_notes": "", - "available_for_pickup": false, - "fees_at_pod_terminal": false, - "holds_at_pod_terminal": false, - "pickup_appointment_at": false, - "location_at_pod_terminal": false, - "available_for_pickup_notes": "", - "fees_at_pod_terminal_notes": "", - "holds_at_pod_terminal_notes": "", - "pickup_appointment_at_notes": "", - "pod_full_out_chassis_number": false, - "location_at_pod_terminal_notes": "", - "pod_full_out_chassis_number_notes": "" - }, - "street": "701 New Dock Street Berths 212-225", - "city": "Terminal Island", - "state": "California", - "state_abbr": "CA", - "zip": "90731", - "country": "United States" - }, - "relationships": { - "port": { - "data": { - "id": "214edbe5-f438-4492-bb18-3cd053c92cc7", - "type": "port" - } - } - } - }, - { - "id": "f800df08-186c-40b9-8d3c-3fcc0838cbe6", - "type": "vessel", - "attributes": { - "name": "YM UNANIMITY", - "imo": "9462718", - "mmsi": "416466000", - "latitude": -78.30435842851921, - "longitude": 25.471353799804547, - "nautical_speed_knots": 25, - "navigational_heading_degrees": 1, - "position_timestamp": "2023-06-05T19:46:18Z" - } - }, - { - "id": "6242e7d4-eeea-40ec-af56-fc0a71e2a36b", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_berthed", - "created_at": "2022-10-21T18:52:26Z", - "voyage_number": "0063E", - "timestamp": "2022-10-17T13:00:00Z", - "data_source": "shipping_line", - "location_locode": "USLAX", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "f028a4c5-4c0b-4894-b4b2-4919d266737e", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "82052525-07c1-4c45-9a09-e7633cbab373", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "f800df08-186c-40b9-8d3c-3fcc0838cbe6", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "214edbe5-f438-4492-bb18-3cd053c92cc7", - "type": "port" - } - }, - "terminal": { - "data": { - "id": "b76896e1-fb93-4ebe-bbb2-d9e7eefc7553", - "type": "terminal" - } - } - } - } - ] -} -``` - -## container.transport.vessel_departed -```json -{ - "data": { - "id": "f65f7fff-2384-4f90-919b-b716c16bc670", - "type": "webhook_notification", - "attributes": { - "id": "f65f7fff-2384-4f90-919b-b716c16bc670", - "event": "container.transport.vessel_departed", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:16:41Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "2b8eb6b3-6dcb-4acc-a234-dfc971408762", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "501aae38-e752-4f15-ab6e-73ea0ede3ca2", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "a65a7f43-0038-4f60-acff-c97a4979d323", - "type": "shipment", - "attributes": { - "created_at": "2022-10-21T20:15:38Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "914595688", - "normalized_number": "914595688", - "shipping_line_scac": "SEAU", - "shipping_line_name": "Sealand Americas", - "shipping_line_short_name": "SeaLand Americas", - "customer_name": "Lang and Sons", - "port_of_lading_locode": "CLARI", - "port_of_lading_name": "Arica", - "port_of_discharge_locode": "USNYC", - "port_of_discharge_name": "New York / New Jersey", - "pod_vessel_name": "NORTHERN PRIORITY", - "pod_vessel_imo": "9450313", - "pod_voyage_number": "242N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2022-09-28T19:51:00Z", - "pol_timezone": "America/Santiago", - "pod_eta_at": "2022-10-27T12:00:00Z", - "pod_original_eta_at": "2022-10-27T12:00:00Z", - "pod_ata_at": null, - "pod_timezone": "America/New_York", - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "cbf247a7-a86c-491f-948e-6a95a41e9199", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "e06c809e-c437-47ac-92a1-aeb1c6f14480", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "a01b7de4-05a5-4871-8074-c524057216ec", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "56ad45c8-dbd0-4c1e-bee7-7c4376db3924", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/2de26519-3fb0-4748-b5ea-fca2b68dcab1" - } - }, - { - "id": "56ad45c8-dbd0-4c1e-bee7-7c4376db3924", - "type": "container", - "attributes": { - "number": "MNBU4188482", - "seal_number": null, - "created_at": "2022-10-21T20:15:38Z", - "ref_numbers": [ - - ], - "pod_arrived_at": null, - "pod_discharged_at": null, - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": false, - "equipment_type": "reefer", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": null, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "pod_last_tracking_request_at": null, - "shipment_last_tracking_request_at": null, - "availability_known": false, - "pod_timezone": "America/New_York", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/New_York" - }, - "relationships": { - "shipment": { - "data": { - "id": "a65a7f43-0038-4f60-acff-c97a4979d323", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "a01b7de4-05a5-4871-8074-c524057216ec", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "875a1706-b8cd-4a27-9b0d-b6bc76b01a91", - "type": "transport_event" - }, - { - "id": "571209fb-d43e-4361-8bc6-f17a72005964", - "type": "transport_event" - }, - { - "id": "2b8eb6b3-6dcb-4acc-a234-dfc971408762", - "type": "transport_event" - }, - { - "id": "7d77608f-647c-45a3-9308-12d988d5be62", - "type": "transport_event" - }, - { - "id": "74c00224-82e2-4bb8-aa2a-291aa4f5554d", - "type": "transport_event" - }, - { - "id": "1580e710-6e00-4e7c-b0e4-cabed65d09e3", - "type": "transport_event" - }, - { - "id": "efa9723b-fb8d-4add-8059-8a95bcb78cba", - "type": "transport_event" - }, - { - "id": "3a19cba1-a2e8-40e9-b9cb-8cadbe802ca9", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "f4bf9733-cfef-4e01-ad8c-efc874dd4b83", - "type": "raw_event" - }, - { - "id": "29f37e62-08a5-45c5-87dd-eee6a8cddc9c", - "type": "raw_event" - }, - { - "id": "c692006a-0755-4f0f-8b6f-e22360de589e", - "type": "raw_event" - }, - { - "id": "b41de73f-aa1d-473e-8fed-9bf9ba6f5384", - "type": "raw_event" - }, - { - "id": "2c60b856-6120-44ba-8c7f-a8e0a7dc3306", - "type": "raw_event" - }, - { - "id": "3d13f46c-b04a-435e-9bc9-7d4c2fb70fe4", - "type": "raw_event" - }, - { - "id": "00104149-7bfd-4d11-989a-0bcf3089f446", - "type": "raw_event" - }, - { - "id": "5a224d3b-d3b7-489a-a776-09f053e422b1", - "type": "raw_event" - }, - { - "id": "85cfe2ce-c88e-4b55-b25f-f36a0c3f5e6a", - "type": "raw_event" - }, - { - "id": "b4481a38-511b-4d84-b92c-e36b6e050b5c", - "type": "raw_event" - }, - { - "id": "527e2d94-93e2-40f8-9421-a80fd53e2fe8", - "type": "raw_event" - }, - { - "id": "86666d95-cb85-4106-b68c-816702e851bf", - "type": "raw_event" - }, - { - "id": "afe2b57f-5363-4ad9-ad0e-a83c8928c0d9", - "type": "raw_event" - }, - { - "id": "e1240511-7a64-49f7-8dc1-bdf31848f1a7", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "cbf247a7-a86c-491f-948e-6a95a41e9199", - "type": "port", - "attributes": { - "id": "cbf247a7-a86c-491f-948e-6a95a41e9199", - "name": "Arica", - "code": "CLARI", - "state_abbr": null, - "city": null, - "country_code": "CL", - "latitude": "-18.471872947", - "longitude": "-70.327958963", - "time_zone": "America/Santiago" - } - }, - { - "id": "95016ff7-1084-416a-9c85-4af24e91d883", - "type": "vessel", - "attributes": { - "name": "MERIDIAN", - "imo": "7002605", - "mmsi": "218415000", - "latitude": -78.30435842851921, - "longitude": 25.471353799804547, - "nautical_speed_knots": 44, - "navigational_heading_degrees": 1, - "position_timestamp": "2023-06-05T19:46:18Z" - } - }, - { - "id": "2b8eb6b3-6dcb-4acc-a234-dfc971408762", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_departed", - "created_at": "2022-10-21T20:15:38Z", - "voyage_number": "239N", - "timestamp": "2022-09-28T19:51:00Z", - "data_source": "shipping_line", - "location_locode": "CLARI", - "timezone": "America/Santiago" - }, - "relationships": { - "shipment": { - "data": { - "id": "a65a7f43-0038-4f60-acff-c97a4979d323", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "56ad45c8-dbd0-4c1e-bee7-7c4376db3924", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "95016ff7-1084-416a-9c85-4af24e91d883", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "cbf247a7-a86c-491f-948e-6a95a41e9199", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - } - ] -} -``` - -## container.transport.vessel_discharged -```json -{ - "data": { - "id": "5c048ec8-afb0-48ca-8657-fa262dc9bbd7", - "type": "webhook_notification", - "attributes": { - "id": "5c048ec8-afb0-48ca-8657-fa262dc9bbd7", - "event": "container.transport.vessel_discharged", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:14:17Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "d637c864-ef35-4df1-9563-ba8bbb179af5", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "851d3a8d-4f95-4296-be6d-0510ece0376d", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "56c8b2c7-a5e0-4c51-9793-f7dadc1fa52c", - "type": "shipment", - "attributes": { - "created_at": "2022-09-01T15:22:22Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "TA2PTC560500", - "normalized_number": "TA2PTC560500", - "shipping_line_scac": "ONEY", - "shipping_line_name": "Ocean Network Express", - "shipping_line_short_name": "ONE", - "customer_name": "Schaden and Sons", - "port_of_lading_locode": "CNQIN", - "port_of_lading_name": "Qingdao", - "port_of_discharge_locode": "USNYC", - "port_of_discharge_name": "New York / New Jersey", - "pod_vessel_name": "ESSEN EXPRESS", - "pod_vessel_imo": "9501370", - "pod_voyage_number": "042E", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2022-07-30T12:24:00Z", - "pol_timezone": "Asia/Shanghai", - "pod_eta_at": "2022-09-22T15:00:00Z", - "pod_original_eta_at": "2022-09-23T10:00:00Z", - "pod_ata_at": "2022-10-19T19:55:00Z", - "pod_timezone": "America/New_York", - "line_tracking_last_attempted_at": "2022-10-21T20:14:12Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "25a0e8eb-a957-4973-a8fd-e984552baafa", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "27f76ae6-d6d9-4938-878b-7113cd628159", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "ad02bb7c-a8a3-4ec5-bbb9-220f3dbf797d", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "7e9be7ac-2845-4130-b032-fb447d3c3126", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/44ddc574-6636-44f0-be9f-6dd6a7e5f0fb" - } - }, - { - "id": "7e9be7ac-2845-4130-b032-fb447d3c3126", - "type": "container", - "attributes": { - "number": "NYKU0800893", - "seal_number": "CND674488", - "created_at": "2022-09-01T15:22:23Z", - "ref_numbers": [ - - ], - "pod_arrived_at": "2022-10-19T19:55:00Z", - "pod_discharged_at": "2022-10-21T15:19:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": true, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": 18928, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": "2022-10-21T19:45:07Z", - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": "2022-10-27T04:00:00Z", - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "In Yard", - "pod_last_tracking_request_at": "2022-10-21T19:45:07Z", - "shipment_last_tracking_request_at": null, - "availability_known": true, - "pod_timezone": "America/New_York", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/New_York" - }, - "relationships": { - "shipment": { - "data": { - "id": "56c8b2c7-a5e0-4c51-9793-f7dadc1fa52c", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "ad02bb7c-a8a3-4ec5-bbb9-220f3dbf797d", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "81879bc8-3b1e-44e7-8b82-18747c5a55c1", - "type": "transport_event" - }, - { - "id": "400895bd-70b7-4ef7-8fef-8edd63eb1450", - "type": "transport_event" - }, - { - "id": "b2d26b74-3189-410e-a4e6-344e2a5ecbc9", - "type": "transport_event" - }, - { - "id": "90e32553-9d25-4426-b625-128d26f9f9c5", - "type": "transport_event" - }, - { - "id": "72375da8-6916-4db4-80a5-903602035b91", - "type": "transport_event" - }, - { - "id": "a0718da3-3185-4038-9b12-fc1886895933", - "type": "transport_event" - }, - { - "id": "eb452da3-ad22-457b-b3b5-6ef7d691efef", - "type": "transport_event" - }, - { - "id": "e474a621-fdad-413b-8a86-80ca59444d2e", - "type": "transport_event" - }, - { - "id": "9f274f93-4763-4af3-a513-47be9de6db74", - "type": "transport_event" - }, - { - "id": "c9a093b5-8962-4e67-9c65-cc82811352ae", - "type": "transport_event" - }, - { - "id": "d637c864-ef35-4df1-9563-ba8bbb179af5", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "4d66e330-b0b0-47bc-bd67-1385b6da145f", - "type": "raw_event" - }, - { - "id": "7db280b7-5728-4d5c-a3f0-80086e726144", - "type": "raw_event" - }, - { - "id": "93cbaa5e-67c8-4aa0-b264-aed51f0a25eb", - "type": "raw_event" - }, - { - "id": "88a6cf44-d652-4b0b-b58e-2e2d5ead3cf0", - "type": "raw_event" - }, - { - "id": "9583e9f1-70c0-426b-801a-99f9193cb7a4", - "type": "raw_event" - }, - { - "id": "e6663d02-6b90-4d4f-a697-1980eca44789", - "type": "raw_event" - }, - { - "id": "d6ad787d-6ad1-479c-87f6-2e104d1275ef", - "type": "raw_event" - }, - { - "id": "6a227c8f-865f-47be-8551-f043be9b9a8b", - "type": "raw_event" - }, - { - "id": "0b6ea128-508c-4951-9c08-a33ba5134447", - "type": "raw_event" - }, - { - "id": "614b23d9-0b23-4800-b5fe-ac2d06178eb3", - "type": "raw_event" - }, - { - "id": "d83e119a-9214-4e07-ae13-024c68b3d231", - "type": "raw_event" - }, - { - "id": "7ea41e5c-b6dd-4972-a42b-e630cbaaa6da", - "type": "raw_event" - }, - { - "id": "12c1202e-5a6b-4551-a78e-670c52dde93a", - "type": "raw_event" - }, - { - "id": "fbff6533-b387-4af5-912b-4040ba3d5a34", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "27f76ae6-d6d9-4938-878b-7113cd628159", - "type": "port", - "attributes": { - "id": "27f76ae6-d6d9-4938-878b-7113cd628159", - "name": "New York / New Jersey", - "code": "USNYC", - "state_abbr": "NY", - "city": "New York", - "country_code": "US", - "latitude": "40.684996498", - "longitude": "-74.151115685", - "time_zone": "America/New_York" - } - }, - { - "id": "ad02bb7c-a8a3-4ec5-bbb9-220f3dbf797d", - "type": "terminal", - "attributes": { - "id": "ad02bb7c-a8a3-4ec5-bbb9-220f3dbf797d", - "nickname": "GCTB", - "name": "GCT Bayonne", - "firms_code": "E364", - "smdg_code": null, - "bic_facility_code": null, - "provided_data": { - "pickup_lfd": false, - "pickup_lfd_notes": "", - "available_for_pickup": false, - "fees_at_pod_terminal": false, - "holds_at_pod_terminal": false, - "pickup_appointment_at": false, - "location_at_pod_terminal": false, - "available_for_pickup_notes": "", - "fees_at_pod_terminal_notes": "", - "holds_at_pod_terminal_notes": "", - "pickup_appointment_at_notes": "", - "pod_full_out_chassis_number": false, - "location_at_pod_terminal_notes": "", - "pod_full_out_chassis_number_notes": "" - }, - "street": "701 New Dock Street Berths 212-225", - "city": "Terminal Island", - "state": "California", - "state_abbr": "CA", - "zip": "90731", - "country": "United States" - }, - "relationships": { - "port": { - "data": { - "id": "27f76ae6-d6d9-4938-878b-7113cd628159", - "type": "port" - } - } - } - }, - { - "id": "043bbbbe-c26f-44fb-a21c-4ee80c8fe319", - "type": "vessel", - "attributes": { - "name": "ESSEN EXPRESS", - "imo": "9501370", - "mmsi": "218474000", - "latitude": -78.30435842851921, - "longitude": 25.471353799804547, - "nautical_speed_knots": 13, - "navigational_heading_degrees": 99, - "position_timestamp": "2023-06-05T19:46:18Z" - } - }, - { - "id": "d637c864-ef35-4df1-9563-ba8bbb179af5", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_discharged", - "created_at": "2022-10-21T20:14:17Z", - "voyage_number": "042E", - "timestamp": "2022-10-21T15:19:00Z", - "data_source": "shipping_line", - "location_locode": "USNYC", - "timezone": "America/New_York" - }, - "relationships": { - "shipment": { - "data": { - "id": "56c8b2c7-a5e0-4c51-9793-f7dadc1fa52c", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "7e9be7ac-2845-4130-b032-fb447d3c3126", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "043bbbbe-c26f-44fb-a21c-4ee80c8fe319", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "27f76ae6-d6d9-4938-878b-7113cd628159", - "type": "port" - } - }, - "terminal": { - "data": { - "id": "ad02bb7c-a8a3-4ec5-bbb9-220f3dbf797d", - "type": "terminal" - } - } - } - } - ] -} -``` - -## container.transport.vessel_loaded -```json -{ - "data": { - "id": "a3baf7bb-3ffe-485e-bf9d-7b7dd17a08a8", - "type": "webhook_notification", - "attributes": { - "id": "a3baf7bb-3ffe-485e-bf9d-7b7dd17a08a8", - "event": "container.transport.vessel_loaded", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:16:47Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "58114b8d-7aab-491d-97ce-7b32c0c1d198", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "7c353e67-cab6-422e-a17f-8483bf250dd9", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "2bae2c8b-c682-47a6-81c7-f1ac9d4404ef", - "type": "shipment", - "attributes": { - "created_at": "2022-10-18T23:20:40Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "HDMUNBOZ32457200", - "normalized_number": "NBOZ32457200", - "shipping_line_scac": "HDMU", - "shipping_line_name": "Hyundai Merchant Marine", - "shipping_line_short_name": "Hyundai", - "customer_name": "Roberts LLC", - "port_of_lading_locode": "CNNGB", - "port_of_lading_name": "Ningbo", - "port_of_discharge_locode": "USLAX", - "port_of_discharge_name": "Los Angeles", - "pod_vessel_name": "NYK THEMIS", - "pod_vessel_imo": "9356696", - "pod_voyage_number": "0082E", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2022-09-23T00:30:00Z", - "pol_timezone": "Asia/Shanghai", - "pod_eta_at": "2022-10-22T12:30:00Z", - "pod_original_eta_at": "2022-10-22T12:30:00Z", - "pod_ata_at": null, - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": "2022-10-21T20:13:02Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "b80acdc3-e2e7-4d09-8c7a-48ce12b4dd38", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "5d0f3e26-a5a7-4aff-a73f-2eacb3ca0a05", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "43cf9fea-eb03-428a-8e57-6da700f95adc", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "e7fd77a6-5b1d-49c6-8790-9597e154c3c6", - "type": "container" - }, - { - "id": "d1a33088-16fa-475c-9ea4-d5098ae9b8df", - "type": "container" - }, - { - "id": "c7876dc6-ccad-4219-b0a2-1e9e9845f474", - "type": "container" - }, - { - "id": "9bbff5af-73fa-463e-b715-d2f6c01c58cc", - "type": "container" - }, - { - "id": "e637137f-ad05-41e3-9a50-045d365d96d9", - "type": "container" - }, - { - "id": "8e4f2995-a25a-4c6c-9382-3c02ce1288af", - "type": "container" - }, - { - "id": "a5970c64-61c2-4f13-b16d-de46871b77f0", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/35285253-d023-4c08-ab12-4ea7ee7793cf" - } - }, - { - "id": "c7876dc6-ccad-4219-b0a2-1e9e9845f474", - "type": "container", - "attributes": { - "number": "KOCU4221161", - "seal_number": "211962498", - "created_at": "2022-10-18T23:20:40Z", - "ref_numbers": [ - - ], - "pod_arrived_at": null, - "pod_discharged_at": null, - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": 20119, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": "2022-10-21T20:09:50Z", - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "On-Board Vessel", - "pod_last_tracking_request_at": "2022-10-21T20:09:50Z", - "shipment_last_tracking_request_at": null, - "availability_known": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "2bae2c8b-c682-47a6-81c7-f1ac9d4404ef", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "43cf9fea-eb03-428a-8e57-6da700f95adc", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "e39a063e-da6b-4b97-bb7a-bf27b1b2d96e", - "type": "transport_event" - }, - { - "id": "58e026b4-4252-4f40-910e-416b75e3f656", - "type": "transport_event" - }, - { - "id": "2032b8a4-150d-40c1-a4d2-9dc89606ba9b", - "type": "transport_event" - }, - { - "id": "58114b8d-7aab-491d-97ce-7b32c0c1d198", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "9b382aaf-50b5-49a2-8b72-8e1ebfe687cb", - "type": "raw_event" - }, - { - "id": "976bc217-7b24-4a2e-8c4f-4f4e3597348d", - "type": "raw_event" - }, - { - "id": "421ab729-8741-4c9b-a493-b8b93ca1ff13", - "type": "raw_event" - }, - { - "id": "0eada15a-7830-4e22-8a30-3c994d7e6130", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "b80acdc3-e2e7-4d09-8c7a-48ce12b4dd38", - "type": "port", - "attributes": { - "id": "b80acdc3-e2e7-4d09-8c7a-48ce12b4dd38", - "name": "Ningbo", - "code": "CNNGB", - "state_abbr": null, - "city": null, - "country_code": "CN", - "latitude": "29.889437243", - "longitude": "122.033720842", - "time_zone": "Asia/Shanghai" - } - }, - { - "id": "c17c5324-008e-4549-9e67-302cff53a56d", - "type": "vessel", - "attributes": { - "name": "NYK THEMIS", - "imo": "9356696", - "mmsi": "636018225", - "latitude": -78.30435842851921, - "longitude": 25.471353799804547, - "nautical_speed_knots": 100, - "navigational_heading_degrees": 18, - "position_timestamp": "2023-06-05T19:46:18Z" - } - }, - { - "id": "58114b8d-7aab-491d-97ce-7b32c0c1d198", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_loaded", - "created_at": "2022-10-21T20:16:47Z", - "voyage_number": "0082E", - "timestamp": "2022-09-22T09:38:00Z", - "data_source": "shipping_line", - "location_locode": "CNNGB", - "timezone": "Asia/Shanghai" - }, - "relationships": { - "shipment": { - "data": { - "id": "2bae2c8b-c682-47a6-81c7-f1ac9d4404ef", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "c7876dc6-ccad-4219-b0a2-1e9e9845f474", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "c17c5324-008e-4549-9e67-302cff53a56d", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "b80acdc3-e2e7-4d09-8c7a-48ce12b4dd38", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - } - ] -} -``` - -## container.updated -```json -{ - "data": { - "id": "aee69c9e-66e5-4ead-82ee-668dafc242ee", - "type": "webhook_notification", - "attributes": { - "id": "aee69c9e-66e5-4ead-82ee-668dafc242ee", - "event": "container.updated", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:19:13Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "638dd40b-6d1a-48a5-af2f-c68463059149", - "type": "container_updated_event" - } - }, - "webhook": { - "data": { - "id": "fdd1cf95-7569-4bf9-965b-825a55fa6303", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "c0633538-4e53-4c33-bde5-055a5bdbfa29", - "type": "shipment", - "attributes": { - "created_at": "2022-09-06T08:29:58Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "MAEUJAK015053", - "normalized_number": "JAK015053", - "shipping_line_scac": "MAEU", - "shipping_line_name": "Maersk", - "shipping_line_short_name": "Maersk", - "customer_name": "Lakin and Sons", - "port_of_lading_locode": "IDJKT", - "port_of_lading_name": "Jakarta, Java", - "port_of_discharge_locode": "USNYC", - "port_of_discharge_name": "New York / New Jersey", - "pod_vessel_name": "MAERSK SYDNEY", - "pod_vessel_imo": "9289958", - "pod_voyage_number": "235W", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2022-09-04T16:54:00Z", - "pol_timezone": "Asia/Jakarta", - "pod_eta_at": "2022-10-10T22:00:00Z", - "pod_original_eta_at": "2022-10-14T07:00:00Z", - "pod_ata_at": "2022-10-10T22:00:00Z", - "pod_timezone": "America/New_York", - "line_tracking_last_attempted_at": "2022-10-21T18:11:45Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "82bf3631-280e-4d73-81d8-fc16753d08a7", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "9e9e8a64-bc96-417f-87d2-189ebccd0123", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "fb4666e2-f159-429e-9d6c-4a09ced32262", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "5f3d40ad-2e2f-4778-8973-8a3a70bef56d", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/7297af4f-e047-4a8e-9eda-d1013ce2ab16" - } - }, - { - "id": "9e9e8a64-bc96-417f-87d2-189ebccd0123", - "type": "port", - "attributes": { - "id": "9e9e8a64-bc96-417f-87d2-189ebccd0123", - "name": "New York / New Jersey", - "code": "USNYC", - "state_abbr": "NY", - "city": "New York", - "country_code": "US", - "latitude": "40.684996498", - "longitude": "-74.151115685", - "time_zone": "America/New_York" - } - }, - { - "id": "fb4666e2-f159-429e-9d6c-4a09ced32262", - "type": "terminal", - "attributes": { - "id": "fb4666e2-f159-429e-9d6c-4a09ced32262", - "nickname": "APM Terminals", - "name": "Port Elizabeth", - "firms_code": "E425", - "smdg_code": null, - "bic_facility_code": null, - "provided_data": { - "pickup_lfd": false, - "pickup_lfd_notes": "", - "available_for_pickup": false, - "fees_at_pod_terminal": false, - "holds_at_pod_terminal": false, - "pickup_appointment_at": false, - "location_at_pod_terminal": false, - "available_for_pickup_notes": "", - "fees_at_pod_terminal_notes": "", - "holds_at_pod_terminal_notes": "", - "pickup_appointment_at_notes": "", - "pod_full_out_chassis_number": false, - "location_at_pod_terminal_notes": "", - "pod_full_out_chassis_number_notes": "" - }, - "street": "701 New Dock Street Berths 212-225", - "city": "Terminal Island", - "state": "California", - "state_abbr": "CA", - "zip": "90731", - "country": "United States" - }, - "relationships": { - "port": { - "data": { - "id": "9e9e8a64-bc96-417f-87d2-189ebccd0123", - "type": "port" - } - } - } - }, - { - "id": "5f3d40ad-2e2f-4778-8973-8a3a70bef56d", - "type": "container", - "attributes": { - "number": "MSKU4807969", - "seal_number": null, - "created_at": "2022-09-06T08:29:58Z", - "ref_numbers": [ - - ], - "pod_arrived_at": "2022-10-10T22:00:00Z", - "pod_discharged_at": "2022-10-11T21:32:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": true, - "equipment_type": "dry", - "equipment_length": 45, - "equipment_height": "high_cube", - "weight_in_lbs": null, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": "2022-10-21T20:19:12Z", - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": "2022-10-24T04:00:00Z", - "pickup_appointment_at": "2022-10-18T16:00:00Z", - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "Yard Grounded (G90402B1)", - "pod_last_tracking_request_at": "2022-10-21T20:19:12Z", - "shipment_last_tracking_request_at": null, - "availability_known": true, - "pod_timezone": "America/New_York", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/New_York" - }, - "relationships": { - "shipment": { - "data": { - "id": "c0633538-4e53-4c33-bde5-055a5bdbfa29", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "fb4666e2-f159-429e-9d6c-4a09ced32262", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "e9b1b49a-b84e-469e-ac72-b0026cd0a17a", - "type": "transport_event" - }, - { - "id": "dbf8a2b9-d19e-42c1-8bdf-216f075028c8", - "type": "transport_event" - }, - { - "id": "4a10db53-cfd1-4a1e-98e3-6dcea72b34b5", - "type": "transport_event" - }, - { - "id": "f32b184d-aea7-4a61-98c3-a2e5e7b78775", - "type": "transport_event" - }, - { - "id": "87245720-cc9f-4e17-b2df-c76a969b6294", - "type": "transport_event" - }, - { - "id": "74d08ccb-0e8c-4715-9d48-d083441764c3", - "type": "transport_event" - }, - { - "id": "f700c2f0-edbe-406f-943c-8575e4656af8", - "type": "transport_event" - }, - { - "id": "8242486b-0410-40e5-9566-c57cda4a2948", - "type": "transport_event" - }, - { - "id": "b6e33165-7848-48b0-a621-2012e6392d6e", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "e6ecd493-4e6b-4d92-809a-ffd73d401d67", - "type": "raw_event" - }, - { - "id": "abff03e4-d246-4d2a-b02b-5a5f81d493b5", - "type": "raw_event" - }, - { - "id": "603d631b-6a81-4389-94f5-7fe21e71a43e", - "type": "raw_event" - }, - { - "id": "3fcc6702-859c-4088-81ea-407f37d83481", - "type": "raw_event" - }, - { - "id": "108f5bab-7a33-4fef-bbfc-fc1b12f8742d", - "type": "raw_event" - }, - { - "id": "e588e237-7dd1-4982-8fc8-6ce82447cd92", - "type": "raw_event" - }, - { - "id": "c796106f-39ce-402e-8296-5a02cafe6da7", - "type": "raw_event" - }, - { - "id": "2fb9cdf8-8f9b-4ff2-9a79-e79031f745c6", - "type": "raw_event" - }, - { - "id": "aa56172c-c0c4-4fe8-a718-ff95826469ad", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "638dd40b-6d1a-48a5-af2f-c68463059149", - "type": "container_updated_event", - "attributes": { - "changeset": { - "available_for_pickup": [ - false, - true - ], - "holds_at_pod_terminal": [ - [ - { - "name": "other", - "status": "hold", - "description": "DOWN - Broken Machine Over Pile" - }, - { - "name": "other", - "status": "hold", - "description": "MACHINE - " - } - ], - [ - - ] - ] - }, - "timestamp": "2022-10-21T20:19:12Z", - "data_source": "terminal", - "timezone": "America/New_York" - }, - "relationships": { - "container": { - "data": { - "id": "5f3d40ad-2e2f-4778-8973-8a3a70bef56d", - "type": "container" - } - }, - "terminal": { - "data": { - "id": "fb4666e2-f159-429e-9d6c-4a09ced32262", - "type": "terminal" - } - }, - "shipment": { - "data": { - "id": "c0633538-4e53-4c33-bde5-055a5bdbfa29", - "type": "shipment" - } - } - } - } - ] -} -``` - -## shipment.estimated.arrival -```json -{ - "data": { - "id": "0a6b1c26-25c1-4309-b190-ff7cb50f75e3", - "type": "webhook_notification", - "attributes": { - "id": "0a6b1c26-25c1-4309-b190-ff7cb50f75e3", - "event": "shipment.estimated.arrival", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:19:13Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "0db4d1a2-ec3e-4123-9d80-1431c81733e6", - "type": "estimated_event" - } - }, - "webhook": { - "data": { - "id": "5e58fc0c-0686-4156-96cf-410f673d54cb", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "fd0b571a-d6bc-4059-93ed-fc5b00a83b15", - "type": "shipment", - "attributes": { - "created_at": "2022-09-20T02:55:25Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "OOLU2136060630", - "normalized_number": "2136060630", - "shipping_line_scac": "OOLU", - "shipping_line_name": "Orient Overseas Container Line", - "shipping_line_short_name": "OOCL", - "customer_name": "Casper, Abshire and Dibbert", - "port_of_lading_locode": "MYPEN", - "port_of_lading_name": "Penang", - "port_of_discharge_locode": "USLAX", - "port_of_discharge_name": "Los Angeles", - "pod_vessel_name": "CMA CGM NORMA", - "pod_vessel_imo": "9299812", - "pod_voyage_number": "0TUPFE1MA", - "destination_locode": "USEWI", - "destination_name": "Elwood", - "destination_timezone": "America/Chicago", - "destination_ata_at": null, - "destination_eta_at": "2022-11-07T17:00:00Z", - "pol_etd_at": "2022-09-24T18:30:00Z", - "pol_atd_at": "2022-09-24T23:35:00Z", - "pol_timezone": "Asia/Kuala_Lumpur", - "pod_eta_at": "2022-11-01T14:00:00Z", - "pod_original_eta_at": "2022-11-03T01:00:00Z", - "pod_ata_at": null, - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": "2022-10-21T20:18:58Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "7d0a06e4-f894-46ee-96f5-407d7cc7db1b", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "56adff5b-0ec1-4b81-985f-3380bca38b0b", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "e37931e1-d68e-497f-bd7c-6bb4ebf00520", - "type": "terminal" - } - }, - "destination": { - "data": { - "id": "fc2e0c64-0491-400c-afe8-a5e8dca77c7c", - "type": "metro_area" - } - }, - "destination_terminal": { - "data": { - "id": "ed03e950-a528-4b3d-bf6b-15141e397dc5", - "type": "rail_terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "300f6e6f-04ba-4f49-b9bd-7302fc382c4d", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/4f363cf9-8d55-4709-bf64-77f4bd7ed824" - } - }, - { - "id": "56adff5b-0ec1-4b81-985f-3380bca38b0b", - "type": "port", - "attributes": { - "id": "56adff5b-0ec1-4b81-985f-3380bca38b0b", - "name": "Los Angeles", - "code": "USLAX", - "state_abbr": "CA", - "city": "Los Angeles", - "country_code": "US", - "latitude": "33.728193631", - "longitude": "-118.255820307", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "0db4d1a2-ec3e-4123-9d80-1431c81733e6", - "type": "estimated_event", - "attributes": { - "created_at": "2022-10-21T20:19:13Z", - "estimated_timestamp": "2022-11-01T14:00:00Z", - "voyage_number": "0TUPFE1MA", - "event": "shipment.estimated.arrival", - "location_locode": "USLAX", - "data_source": "shipping_line", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "fd0b571a-d6bc-4059-93ed-fc5b00a83b15", - "type": "shipment" - } - }, - "port": { - "data": { - "id": "56adff5b-0ec1-4b81-985f-3380bca38b0b", - "type": "port" - } - }, - "vessel": { - "data": { - "id": "3f97ddb7-2e53-4ed1-ae22-04b82b340136", - "type": "vessel" - } - } - } - } - ] -} -``` - -## shipment.transport.vessel_arrived -```json -{ - "data": { - "id": "1ababdd7-3d93-436d-8fc7-e8029bb4b466", - "type": "webhook_notification", - "attributes": { - "id": "1ababdd7-3d93-436d-8fc7-e8029bb4b466", - "event": "shipment.transport.vessel_arrived", - "delivery_status": "succeeded", - "created_at": "2020-05-11T15:09:58Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "e400b938-19d9-4d78-888f-351af48a915e", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "a3ea832c-179c-4fd9-891d-5765a77af9d4", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - } -} -``` - -## tracking_request.awaiting_manifest -```json -{ - "data": { - "id": "b7235d00-2617-434e-9e28-a5cf83a0a0d3", - "type": "webhook_notification", - "attributes": { - "id": "b7235d00-2617-434e-9e28-a5cf83a0a0d3", - "event": "tracking_request.awaiting_manifest", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:15:54Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "ff77f76c-5e73-47c4-ab1e-d499cb1fa10f", - "type": "tracking_request" - } - }, - "webhook": { - "data": { - "id": "bb30c47d-db43-4fa0-9287-7bfade47e4ec", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "ff77f76c-5e73-47c4-ab1e-d499cb1fa10f", - "type": "tracking_request", - "attributes": { - "request_number": "IZ12208APRV6", - "request_type": "bill_of_lading", - "scac": "HLCU", - "ref_numbers": [ - - ], - "shipment_tags": [ - - ], - "created_at": "2022-10-21T20:15:49Z", - "updated_at": "2022-10-21T21:15:49Z", - "status": "awaiting_manifest", - "failed_reason": null, - "is_retrying": false, - "retry_count": null - }, - "relationships": { - "tracked_object": { - "data": null - }, - "customer": { - "data": null - }, - "user": { - "data": null - } - }, - "links": { - "self": "/v2/tracking_requests/ca388055-8e3a-4b85-8401-e4aa1abf7228" - } - } - ] -} -``` - -## tracking_request.failed -```json -{ - "data": { - "id": "dfa9f92b-dbc5-403e-b03f-d5683abbd074", - "type": "webhook_notification", - "attributes": { - "id": "dfa9f92b-dbc5-403e-b03f-d5683abbd074", - "event": "tracking_request.failed", - "delivery_status": "pending", - "created_at": "2022-10-21T20:19:14Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "5fe62a78-86af-4408-a79c-2d03c907a68b", - "type": "tracking_request" - } - }, - "webhook": { - "data": { - "id": "5c7cbf41-37f4-4e76-b06b-4b17860fab02", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "5fe62a78-86af-4408-a79c-2d03c907a68b", - "type": "tracking_request", - "attributes": { - "request_number": "MAEU11875506", - "request_type": "bill_of_lading", - "scac": "MAEU", - "ref_numbers": [ - - ], - "shipment_tags": [ - - ], - "created_at": "2022-10-21T20:19:13Z", - "updated_at": "2022-10-21T21:19:13Z", - "status": "failed", - "failed_reason": "not_found", - "is_retrying": false, - "retry_count": null - }, - "relationships": { - "tracked_object": { - "data": null - }, - "customer": { - "data": null - }, - "user": { - "data": null - } - }, - "links": { - "self": "/v2/tracking_requests/93d0d469-cbcf-4b6c-ae59-526c176942c7" - } - } - ] -} -``` - -## tracking_request.succeeded -```json -{ - "data": { - "id": "0b27a595-e531-4f93-8d5a-22e1675d863a", - "type": "webhook_notification", - "attributes": { - "id": "0b27a595-e531-4f93-8d5a-22e1675d863a", - "event": "tracking_request.succeeded", - "delivery_status": "succeeded", - "created_at": "2022-10-21T20:18:36Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "bf1d2f9d-f88a-4aed-901b-a86cddb0a665", - "type": "tracking_request" - } - }, - "webhook": { - "data": { - "id": "b1617bb8-d713-4450-8dd7-81be1317631c", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "2444986c-5ebe-4bc5-ad55-d24293424943", - "type": "container", - "attributes": { - "number": "MRKU3700927", - "seal_number": null, - "created_at": "2022-10-21T20:18:36Z", - "ref_numbers": [ - - ], - "pod_arrived_at": null, - "pod_discharged_at": null, - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [ - - ], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": null, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "pod_last_tracking_request_at": null, - "shipment_last_tracking_request_at": null, - "availability_known": false, - "pod_timezone": null, - "final_destination_timezone": null, - "empty_terminated_timezone": null - }, - "relationships": { - "shipment": { - "data": { - "id": "6af82332-9bff-4b4a-a1e1-a382e0f82ca5", - "type": "shipment" - } - }, - "pod_terminal": { - "data": null - }, - "transport_events": { - "data": [ - - ] - }, - "raw_events": { - "data": [ - - ] - } - } - }, - { - "id": "6af82332-9bff-4b4a-a1e1-a382e0f82ca5", - "type": "shipment", - "attributes": { - "created_at": "2022-10-21T20:18:36Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "MAEU221876618", - "normalized_number": "221876618", - "shipping_line_scac": "MAEU", - "shipping_line_name": "Maersk", - "shipping_line_short_name": "Maersk", - "customer_name": "Schuster-Barrows", - "port_of_lading_locode": "CNNGB", - "port_of_lading_name": "Ningbo", - "port_of_discharge_locode": null, - "port_of_discharge_name": null, - "pod_vessel_name": null, - "pod_vessel_imo": null, - "pod_voyage_number": null, - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": null, - "pol_timezone": "Asia/Shanghai", - "pod_eta_at": "2022-11-25T08:00:00Z", - "pod_original_eta_at": "2022-11-25T08:00:00Z", - "pod_ata_at": null, - "pod_timezone": null, - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "d741a6bc-13dd-4b62-a5c2-f65050c9403d", - "type": "port" - } - }, - "port_of_discharge": { - "data": null - }, - "pod_terminal": { - "data": null - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "2444986c-5ebe-4bc5-ad55-d24293424943", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/e5a39855-f438-467a-9c18-ae91cd46cfaf" - } - }, - { - "id": "bf1d2f9d-f88a-4aed-901b-a86cddb0a665", - "type": "tracking_request", - "attributes": { - "request_number": "MAEU221876618", - "request_type": "bill_of_lading", - "scac": "MAEU", - "ref_numbers": [ - - ], - "shipment_tags": [ - - ], - "created_at": "2022-10-17T14:17:30Z", - "updated_at": "2022-10-17T15:17:30Z", - "status": "created", - "failed_reason": null, - "is_retrying": false, - "retry_count": null - }, - "relationships": { - "tracked_object": { - "data": { - "id": "6af82332-9bff-4b4a-a1e1-a382e0f82ca5", - "type": "shipment" - } - }, - "customer": { - "data": null - }, - "user": { - "data": null - } - }, - "links": { - "self": "/v2/tracking_requests/61e7fc09-e1a0-4bfa-b559-49d7576c790e" - } - } - ] -} -``` - -## tracking_request.tracking_stopped -```json -{ - "data": { - "id": "00cbaa34-c487-419c-b5c4-415da5478971", - "type": "webhook_notification", - "attributes": { - "id": "00cbaa34-c487-419c-b5c4-415da5478971", - "event": "tracking_request.tracking_stopped", - "delivery_status": "pending", - "created_at": "2022-11-22T16:39:42Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "94f2d7a0-4a10-42e0-81d8-83cbabc4ef6c", - "type": "tracking_request" - } - }, - "webhook": { - "data": { - "id": "85336ef9-8901-45fc-95c1-d26bc8f2bf68", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ - - ] - } - } - }, - "included": [ - { - "id": "36917159-1982-4c0e-bb7e-7a5e972d9c1b", - "type": "shipment", - "attributes": { - "created_at": "2022-09-15T17:52:08Z", - "ref_numbers": [ - - ], - "tags": [ - - ], - "bill_of_lading_number": "CMDUAMC1863476", - "normalized_number": "AMC1863476", - "shipping_line_scac": "CMDU", - "shipping_line_name": "CMA CGM", - "shipping_line_short_name": "CMA CGM", - "customer_name": "Miller-Gleason", - "port_of_lading_locode": "INNSA", - "port_of_lading_name": "Nhava Sheva", - "port_of_discharge_locode": "USLAX", - "port_of_discharge_name": "Los Angeles", - "pod_vessel_name": "EVER LOVELY", - "pod_vessel_imo": "9629110", - "pod_voyage_number": "0TBD0W1MA", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2022-07-20T17:59:00Z", - "pol_timezone": "Asia/Calcutta", - "pod_eta_at": "2022-09-14T14:00:00Z", - "pod_original_eta_at": "2022-09-14T14:00:00Z", - "pod_ata_at": "2022-09-16T08:11:18Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": "2022-10-21T20:01:32Z", - "line_tracking_last_succeeded_at": null, - "line_tracking_stopped_at": "2022-11-22T16:39:42Z", - "line_tracking_stopped_reason": "account_closed" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "256aec7d-4915-48d4-b8e5-911e097b05e7", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "786ff548-7e55-4d18-b4a6-b6ee31b4cc62", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "46648f15-fcf3-49fc-b86f-e8550b95c40c", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "bb77de85-2e89-4596-8a74-6100f3180296", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/7fd3135d-9da7-4dad-87e3-63242343e182" - } - }, - { - "id": "94f2d7a0-4a10-42e0-81d8-83cbabc4ef6c", - "type": "tracking_request", - "attributes": { - "request_number": "CMDUAMC1863476", - "request_type": "bill_of_lading", - "scac": "CMDU", - "ref_numbers": [ - - ], - "shipment_tags": [ - - ], - "created_at": "2022-09-15T17:52:07Z", - "updated_at": "2022-09-15T18:52:07Z", - "status": "tracking_stopped", - "failed_reason": null, - "is_retrying": false, - "retry_count": null - }, - "relationships": { - "tracked_object": { - "data": { - "id": "36917159-1982-4c0e-bb7e-7a5e972d9c1b", - "type": "shipment" - } - }, - "customer": { - "data": null - }, - "user": { - "data": { - "id": "3d28c8cb-9cbb-471d-b946-80e7345c9572", - "type": "user" - } - } - }, - "links": { - "self": "/v2/tracking_requests/c871c4b8-0436-410c-8b39-fd426e06869e" - } - } - ] -} -``` - -## container.transport.arrived_at_inland_destination -```json -{ - "data": { - "id": "51cc2480-760e-4ce6-af36-a69669292cad", - "type": "webhook_notification", - "attributes": { - "id": "51cc2480-760e-4ce6-af36-a69669292cad", - "event": "container.transport.arrived_at_inland_destination", - "delivery_status": "pending", - "created_at": "2024-06-26T21:21:53Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "1a61d6af-64ee-4f45-846d-59e0b8b257d0", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "9fc3b3b6-5551-4b76-b0c7-d9bb1e86ed26", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [] - } - } - }, - "links": { - "self": "https://api.terminal49.com/v2/webhook_notifications/examples?event=container.transport.arrived_at_inland_destination" - }, - "included": [ - { - "id": "e7a78724-6ddd-48a2-85c9-e88def7a8406", - "type": "shipment", - "attributes": { - "created_at": "2024-06-26T21:21:52Z", - "ref_numbers": [ - "REF-4DB6E7", - "REF-045A0E" - ], - "tags": [], - "bill_of_lading_number": "TE49BB993AAD", - "normalized_number": "TE49BB993AAD", - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "shipping_line_short_name": "MSC", - "customer_name": "Rempel-Becker", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2024-06-13T21:21:52Z", - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2024-07-03T21:21:52Z", - "pod_original_eta_at": "2024-07-03T21:21:52Z", - "pod_ata_at": "2024-07-03T22:21:52Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": "2024-06-26T21:21:52Z", - "line_tracking_last_succeeded_at": "2024-06-26T21:21:52Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "links": { - "self": "/v2/shipments/e7a78724-6ddd-48a2-85c9-e88def7a8406" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "dd85723f-17a6-4b7a-bd98-c6f0d94fe4e6", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": { - "id": "a88159cc-9c76-492f-a145-003352ef8e92", - "type": "terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "30c56cea-b155-4618-a365-a1d77d5bdac5", - "type": "container" - } - ] - } - } - }, - { - "id": "30c56cea-b155-4618-a365-a1d77d5bdac5", - "type": "container", - "attributes": { - "number": "OERU4412200", - "seal_number": "f6ab033e15ec49fd", - "created_at": "2024-06-26T21:21:53Z", - "ref_numbers": [ - "REF-ED3A41" - ], - "pod_arrived_at": "2024-06-26T21:21:52Z", - "pod_discharged_at": "2024-06-26T21:21:52Z", - "final_destination_full_out_at": "2024-06-26T21:21:52Z", - "holds_at_pod_terminal": [], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": 53443, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "pod_last_tracking_request_at": null, - "shipment_last_tracking_request_at": "2024-06-26T21:21:52Z", - "availability_known": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "e7a78724-6ddd-48a2-85c9-e88def7a8406", - "type": "shipment" - } - }, - "pickup_facility": { - "data": { - "id": "a88159cc-9c76-492f-a145-003352ef8e92", - "type": "terminal" - } - }, - "pod_terminal": { - "data": null - }, - "transport_events": { - "data": [ - { - "id": "1a61d6af-64ee-4f45-846d-59e0b8b257d0", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "807f7867-9f4b-467b-b5be-c297eba40533", - "type": "raw_event" - }, - { - "id": "3a03c7a3-8aec-4f33-8603-3050b974cbef", - "type": "raw_event" - }, - { - "id": "45df4091-4b77-4526-b7ec-b3619c14f1fd", - "type": "raw_event" - }, - { - "id": "6ff26f49-ca58-4a3f-92e1-db0f295b6a5c", - "type": "raw_event" - }, - { - "id": "141ad992-befb-4951-8421-bf84dfed1fb8", - "type": "raw_event" - }, - { - "id": "1621ff9e-e45d-4151-a9ee-6f242fa61d0d", - "type": "raw_event" - }, - { - "id": "06f3f0c3-8ab8-4cf4-b442-0de4e7147dbf", - "type": "raw_event" - }, - { - "id": "f6681a35-b760-45f6-a69f-66fa81be5ea8", - "type": "raw_event" - }, - { - "id": "423481e2-4e8f-45c0-8707-92742dc7ba60", - "type": "raw_event" - }, - { - "id": "09bef6df-0dcd-4a84-99da-038a5f64261b", - "type": "raw_event" - }, - { - "id": "c3b334ed-9ade-45f5-bfd1-66d26a28351a", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port", - "attributes": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "name": "Port of Oakland", - "code": "USOAK", - "state_abbr": "CA", - "city": "Oakland", - "country_code": "US", - "latitude": "37.8044", - "longitude": "-122.2712", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", - "type": "terminal", - "attributes": { - "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", - "nickname": "SSA", - "name": "SSA Terminal", - "firms_code": "Z985", - "smdg_code": "B58", - "bic_facility_code": "USOAKTYJE", - "provided_data": { - "pickup_lfd": true, - "pod_full_out_at": true, - "pickup_lfd_notes": "", - "available_for_pickup": true, - "fees_at_pod_terminal": true, - "holds_at_pod_terminal": true, - "pickup_appointment_at": false, - "location_at_pod_terminal": true, - "available_for_pickup_notes": "", - "fees_at_pod_terminal_notes": "", - "holds_at_pod_terminal_notes": "", - "pickup_appointment_at_notes": "", - "pod_full_out_chassis_number": true, - "location_at_pod_terminal_notes": "", - "pod_full_out_chassis_number_notes": "" - }, - "street": "880 Koepp Manors", - "city": "South Dustin", - "state": "West Virginia", - "state_abbr": "AR", - "zip": "71794", - "country": "Lithuania", - "facility_type": "ocean_terminal" - }, - "relationships": { - "port": { - "data": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port" - } - } - } - }, - { - "id": "1a61d6af-64ee-4f45-846d-59e0b8b257d0", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_arrived", - "created_at": "2024-06-26T21:21:52Z", - "voyage_number": null, - "timestamp": "2024-06-26T21:21:52Z", - "data_source": "shipping_line", - "location_locode": "USOAK", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "e7a78724-6ddd-48a2-85c9-e88def7a8406", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "30c56cea-b155-4618-a365-a1d77d5bdac5", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "daf64780-ecdd-4d46-ae4a-eb70968069ed", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port" - } - }, - "terminal": { - "data": { - "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", - "type": "terminal" - } - } - } - } - ] -} -``` - -## container.transport.estimated.arrived_at_inland_destination -```json -{ - "data": { - "id": "2ff96102-8016-41d8-a313-1fcbf4cba2cc", - "type": "webhook_notification", - "attributes": { - "id": "2ff96102-8016-41d8-a313-1fcbf4cba2cc", - "event": "container.transport.estimated.arrived_at_inland_destination", - "delivery_status": "pending", - "created_at": "2024-06-26T21:22:22Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "ce3376bf-ed14-43ea-b0cd-9ebd5105be7b", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "188e6629-d9e0-446e-8dd8-078090eba7b3", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [] - } - } - }, - "links": { - "self": "https://api.terminal49.com/v2/webhook_notifications/examples?event=container.transport.estimated.arrived_at_inland_destination" - }, - "included": [ - { - "id": "a8d9896f-a483-4548-9bae-3107e6adca3b", - "type": "shipment", - "attributes": { - "created_at": "2024-06-26T21:22:21Z", - "ref_numbers": [ - "REF-7B250E" - ], - "tags": [], - "bill_of_lading_number": "TE492FCF3119", - "normalized_number": "TE492FCF3119", - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "shipping_line_short_name": "MSC", - "customer_name": "Gerlach, Hettinger and Mitchell", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2024-06-13T21:22:21Z", - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2024-07-03T21:22:21Z", - "pod_original_eta_at": "2024-07-03T21:22:21Z", - "pod_ata_at": "2024-07-03T22:22:21Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": "2024-06-26T21:22:21Z", - "line_tracking_last_succeeded_at": "2024-06-26T21:22:21Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "links": { - "self": "/v2/shipments/a8d9896f-a483-4548-9bae-3107e6adca3b" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "dd85723f-17a6-4b7a-bd98-c6f0d94fe4e6", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": { - "id": "99a517a6-b7a1-49aa-9012-2d03d7aff720", - "type": "terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "eb48ec6e-3057-44b6-8aee-db1fbd8705e3", - "type": "container" - } - ] - } - } - }, - { - "id": "eb48ec6e-3057-44b6-8aee-db1fbd8705e3", - "type": "container", - "attributes": { - "number": "GLDU9577709", - "seal_number": "fcbe2b0c3fa3e367", - "created_at": "2024-06-26T21:22:22Z", - "ref_numbers": [ - "REF-2A857F", - "REF-ADB003" - ], - "pod_arrived_at": "2024-06-26T21:22:21Z", - "pod_discharged_at": "2024-06-26T21:22:21Z", - "final_destination_full_out_at": "2024-06-26T21:22:21Z", - "holds_at_pod_terminal": [], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": 54472, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "pod_last_tracking_request_at": null, - "shipment_last_tracking_request_at": "2024-06-26T21:22:21Z", - "availability_known": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "a8d9896f-a483-4548-9bae-3107e6adca3b", - "type": "shipment" - } - }, - "pickup_facility": { - "data": { - "id": "99a517a6-b7a1-49aa-9012-2d03d7aff720", - "type": "terminal" - } - }, - "pod_terminal": { - "data": null - }, - "transport_events": { - "data": [ - { - "id": "ce3376bf-ed14-43ea-b0cd-9ebd5105be7b", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "9da5b426-9e08-4d82-913e-fc3e0b1b5b69", - "type": "raw_event" - }, - { - "id": "5f8809ef-4d80-4ffa-8e92-faba48a3909e", - "type": "raw_event" - }, - { - "id": "13651871-42bb-4c97-b3e2-9cc3b5737da7", - "type": "raw_event" - }, - { - "id": "dda7f006-c741-43c4-9ab3-019f391dae13", - "type": "raw_event" - }, - { - "id": "c7ec67f1-bfb0-47ca-9515-7f4f1449dffc", - "type": "raw_event" - }, - { - "id": "44325279-6fbe-46c8-9138-52732d5128b6", - "type": "raw_event" - }, - { - "id": "51539ed3-10c7-4f22-955f-07c27321fbb7", - "type": "raw_event" - }, - { - "id": "ea9f5732-404b-4c7d-a98c-ad960bd0632c", - "type": "raw_event" - }, - { - "id": "3361e110-9cc3-4023-a162-3301adb342f1", - "type": "raw_event" - }, - { - "id": "d6b4cb90-f93d-4e0e-a1c9-1f6593e60e2c", - "type": "raw_event" - }, - { - "id": "5404e2a6-945a-4c2f-923c-15efab1aadf6", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port", - "attributes": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "name": "Port of Oakland", - "code": "USOAK", - "state_abbr": "CA", - "city": "Oakland", - "country_code": "US", - "latitude": "37.8044", - "longitude": "-122.2712", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", - "type": "terminal", - "attributes": { - "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", - "nickname": "SSA", - "name": "SSA Terminal", - "firms_code": "Z985", - "smdg_code": "B58", - "bic_facility_code": "USOAKTYJE", - "provided_data": { - "pickup_lfd": true, - "pod_full_out_at": true, - "pickup_lfd_notes": "", - "available_for_pickup": true, - "fees_at_pod_terminal": true, - "holds_at_pod_terminal": true, - "pickup_appointment_at": false, - "location_at_pod_terminal": true, - "available_for_pickup_notes": "", - "fees_at_pod_terminal_notes": "", - "holds_at_pod_terminal_notes": "", - "pickup_appointment_at_notes": "", - "pod_full_out_chassis_number": true, - "location_at_pod_terminal_notes": "", - "pod_full_out_chassis_number_notes": "" - }, - "street": "806 Lillia Forks", - "city": "South Edison", - "state": "Colorado", - "state_abbr": "KY", - "zip": "47421", - "country": "Mauritius", - "facility_type": "ocean_terminal" - }, - "relationships": { - "port": { - "data": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port" - } - } - } - }, - { - "id": "ce3376bf-ed14-43ea-b0cd-9ebd5105be7b", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_arrived", - "created_at": "2024-06-26T21:22:21Z", - "voyage_number": null, - "timestamp": "2024-06-26T21:22:21Z", - "data_source": "shipping_line", - "location_locode": "USOAK", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "a8d9896f-a483-4548-9bae-3107e6adca3b", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "eb48ec6e-3057-44b6-8aee-db1fbd8705e3", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "daf64780-ecdd-4d46-ae4a-eb70968069ed", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port" - } - }, - "terminal": { - "data": { - "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", - "type": "terminal" - } - } - } - } - ] -} -``` - -## container.pickup_lfd.changed -```json -{ - "data": { - "id": "4f95eaca-ebd1-414d-b50f-84e113a01b37", - "type": "webhook_notification", - "attributes": { - "id": "4f95eaca-ebd1-414d-b50f-84e113a01b37", - "event": "container.pickup_lfd.changed", - "delivery_status": "pending", - "created_at": "2024-06-26T21:22:47Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "82099f21-0a1d-40bd-b56c-30461f7db1cc", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "046cf6b8-ae02-47e2-90d4-d6379319bb71", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [] - } - } - }, - "links": { - "self": "https://api.terminal49.com/v2/webhook_notifications/examples?event=container.pickup_lfd.changed" - }, - "included": [ - { - "id": "29a59f1d-cd71-4c0c-9be8-bc453f945d27", - "type": "shipment", - "attributes": { - "created_at": "2024-06-26T21:22:46Z", - "ref_numbers": [ - "REF-D79122" - ], - "tags": [], - "bill_of_lading_number": "TE491A648538", - "normalized_number": "TE491A648538", - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "shipping_line_short_name": "MSC", - "customer_name": "Muller-Hauck", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2024-06-13T21:22:46Z", - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2024-07-03T21:22:46Z", - "pod_original_eta_at": "2024-07-03T21:22:46Z", - "pod_ata_at": "2024-07-03T22:22:46Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": "2024-06-26T21:22:46Z", - "line_tracking_last_succeeded_at": "2024-06-26T21:22:46Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "links": { - "self": "/v2/shipments/29a59f1d-cd71-4c0c-9be8-bc453f945d27" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "dd85723f-17a6-4b7a-bd98-c6f0d94fe4e6", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": { - "id": "a40d96ef-3f53-4289-b371-8d26a2aaeff8", - "type": "terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "3d2ce91a-6ac8-4053-a6fb-7dac1c47bf71", - "type": "container" - } - ] - } - } - }, - { - "id": "3d2ce91a-6ac8-4053-a6fb-7dac1c47bf71", - "type": "container", - "attributes": { - "number": "OERU6438708", - "seal_number": "16082b290c25f0c5", - "created_at": "2024-06-26T21:22:47Z", - "ref_numbers": [ - "REF-2E50D1" - ], - "pod_arrived_at": "2024-06-26T21:22:46Z", - "pod_discharged_at": "2024-06-26T21:22:46Z", - "final_destination_full_out_at": "2024-06-26T21:22:46Z", - "holds_at_pod_terminal": [], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": 60753, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "pod_last_tracking_request_at": null, - "shipment_last_tracking_request_at": "2024-06-26T21:22:46Z", - "availability_known": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "29a59f1d-cd71-4c0c-9be8-bc453f945d27", - "type": "shipment" - } - }, - "pickup_facility": { - "data": { - "id": "a40d96ef-3f53-4289-b371-8d26a2aaeff8", - "type": "terminal" - } - }, - "pod_terminal": { - "data": null - }, - "transport_events": { - "data": [ - { - "id": "82099f21-0a1d-40bd-b56c-30461f7db1cc", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "4327e350-4a73-4960-9842-e2a228477e8a", - "type": "raw_event" - }, - { - "id": "5e51cdbb-3dd9-42d5-9d1e-30aca590b874", - "type": "raw_event" - }, - { - "id": "f2b4c0e4-2f53-4653-82d4-31c5d68a9886", - "type": "raw_event" - }, - { - "id": "350e6c50-8594-48c7-845d-c657f841f46c", - "type": "raw_event" - }, - { - "id": "57794561-bae5-4f9b-9d14-24b35da518eb", - "type": "raw_event" - }, - { - "id": "8c50848d-15b4-4155-a504-e8df4c9ae7e4", - "type": "raw_event" - }, - { - "id": "c3abbd18-198c-441e-8c46-9033ee5bdfbc", - "type": "raw_event" - }, - { - "id": "9160cf78-5414-4eb1-853a-519a03e83879", - "type": "raw_event" - }, - { - "id": "69f5b333-53a0-40ee-a759-375f6cae04df", - "type": "raw_event" - }, - { - "id": "1f7c900c-2940-4c6b-a132-397a9ca075aa", - "type": "raw_event" - }, - { - "id": "2c9e5f2f-df5d-4992-b33e-088f37e123f8", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port", - "attributes": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "name": "Port of Oakland", - "code": "USOAK", - "state_abbr": "CA", - "city": "Oakland", - "country_code": "US", - "latitude": "37.8044", - "longitude": "-122.2712", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", - "type": "terminal", - "attributes": { - "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", - "nickname": "SSA", - "name": "SSA Terminal", - "firms_code": "Z985", - "smdg_code": "B58", - "bic_facility_code": "USOAKTYJE", - "provided_data": { - "pickup_lfd": true, - "pod_full_out_at": true, - "pickup_lfd_notes": "", - "available_for_pickup": true, - "fees_at_pod_terminal": true, - "holds_at_pod_terminal": true, - "pickup_appointment_at": false, - "location_at_pod_terminal": true, - "available_for_pickup_notes": "", - "fees_at_pod_terminal_notes": "", - "holds_at_pod_terminal_notes": "", - "pickup_appointment_at_notes": "", - "pod_full_out_chassis_number": true, - "location_at_pod_terminal_notes": "", - "pod_full_out_chassis_number_notes": "" - }, - "street": "636 Volkman Valleys", - "city": "Lake Jame", - "state": "Indiana", - "state_abbr": "TN", - "zip": "34546-1736", - "country": "Saint Lucia", - "facility_type": "ocean_terminal" - }, - "relationships": { - "port": { - "data": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port" - } - } - } - }, - { - "id": "82099f21-0a1d-40bd-b56c-30461f7db1cc", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_arrived", - "created_at": "2024-06-26T21:22:46Z", - "voyage_number": null, - "timestamp": "2024-06-26T21:22:46Z", - "data_source": "shipping_line", - "location_locode": "USOAK", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "29a59f1d-cd71-4c0c-9be8-bc453f945d27", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "3d2ce91a-6ac8-4053-a6fb-7dac1c47bf71", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "daf64780-ecdd-4d46-ae4a-eb70968069ed", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port" - } - }, - "terminal": { - "data": { - "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", - "type": "terminal" - } - } - } - } - ] -} -``` - -## container.transport.available - -```json -{ - "data": { - "id": "fb111726-d489-4ef7-bac9-d39f2cbe66ba", - "type": "webhook_notification", - "attributes": { - "id": "fb111726-d489-4ef7-bac9-d39f2cbe66ba", - "event": "container.transport.available", - "delivery_status": "succeeded", - "created_at": "2025-02-26T12:51:52Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "7adced6b-0ae4-4554-8c51-f85e58e57eb7", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "91357e6c-43f9-49c2-b052-a2941a003751", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [] - } - } - }, - "links": { - "self": "https://api.terminal49.com/v2/webhook_notifications/fb111726-d489-4ef7-bac9-d39f2cbe66ba" - } -} -``` \ No newline at end of file diff --git a/docs/datasync/home.mdx b/docs/datasync/home.mdx deleted file mode 100644 index 6b75c2bc..00000000 --- a/docs/datasync/home.mdx +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Terminal49 Dev Documentation -og:title: Datasync Home | Terminal49 API Documentation -og:description: Discover the Datasync feature from Terminal49. Streamline shipment data management effortlessly. ---- -We offer two fantastic ways to track your shipments from origin to destination. - - 1. [Terminal49 DataSync](/datasync/overview). Get tables full of fresh information delivered into your current data system. Easy to set up, and perfect for complementing your current data. - 2. [Terminal49 API](/api-docs/getting-started/start-here). Connect directly with the API, pull data for specific shipments and containers, and get updates via webhooks. - -If you already have a data store that feeds the rest of your system, DataSync is probably what you want. - -## What can I use Terminal49 data for? -Here are just a few of the data points we return and possible use-cases. - -| DATA | EXAMPLE USE CASE | -|----------------------------------------|--------------------------------------------------------------------------| -| Destination ETA | Surface ETA changes to your relevant teams as they're reported | -| Last Free Day and terminal status¹ | Track containers approaching LFD and prioritize dispatching | -| Fees and holds at destination terminal | Clear your cargo to keep you containers moving | -| Actual departure and arrival times | Report journey times by route to compare your ocean carriers performance | - -_1. At container ports in the US_ - -## How it works -All you need to provide are your BOL numbers and SCACs. Terminal 49 will lookup the shipment with the carrier and populate shipment details including containers. - -Once the shipment is set up, Terminal 49 periodically checks with the carrier and the destination terminal. - -If any of the details of your shipment or containers change (for example - if the ETA changes) we'll ensure you're always kept up to date. - - - If you're using DataSync, we'll update the data in your system - - If you're using the API, we'll post the shipment to the the webhook you provide - -👈🏽 Please click API Docs or Data Sync on the left to get started! \ No newline at end of file diff --git a/docs/datasync/overview.mdx b/docs/datasync/overview.mdx deleted file mode 100644 index 966ac8f9..00000000 --- a/docs/datasync/overview.mdx +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: Overview -og:title: Datasync Overview | Terminal49 API Documentation -og:description: Get an overview of the Datasync feature in Terminal49's API to streamline container tracking. ---- -DataSync is the easiest way to get fresh, up-to-date container and shipment data into your system. - -DataSync will create 3 tables in your system, in the schema / dataset / folder / spreadsheet of your choice: [containers](/datasync/table-properties/containers_rail), [shipments](/datasync/table-properties/shipments), and [tracking_requests](/datasync/table-properties/tracking-requests). In addition to these 3 tables, a technical table named [_transfer_status](/datasync/table-properties/transfer-status) is also created, which tells you when each table was last refreshed. - -We can send the data to almost any database, data warehouse, or object store, as well as to Google Sheets. See the [full list of supported systems](/datasync/supported-destinations). - - -## How often does the data update? -DataSync will keep the data tables updated with a refresh every hour. - -Each refresh reloads only the rows that have been changed since the previous refresh, so you won't have excess writes to your system. - -To check when a table was last updated, check the [_transfer_status](/datasync/table-properties/transfer-status) table - for each row in that table there is a unique table key, and the time when the latest sync occurred for that table. - - -## How to use the data -You can use the container and shipment tracking data any way you like, but here are a couple ideas: - - - Send data directly to your visualization/analytics/reports software like PowerBI or Tableau - - Send data directly to your TMS or ERP - - Join data with one of your own tables - - Use a Database View or Pivot Table to narrow down what you're looking at, or rename columns - - Use Database Triggers to respond to updates - - -## The setup process -For users that are already tracking shipments with Terminal49, the setup is a 3-step process that takes less than 2 hours on average. Some simpler setups are done in 20 minutes. See below for ways to get data into the system. - - 1. **Connect data systems**. This could mean doing role-based auth or sharing credentials for a single-purpose user. See our [security FAQ](https://help.terminal49.com/en/articles/7988732-security-considerations-for-terminal49-datasync) if you want to know more about how we keep your data secure. - - 2. **1-hour configuration call**. We make sure you're getting data the way you want, configuring it to fit in with how you store all your current data. - - 3. **Start querying the data**. And then you're ready to go! Nothing new to learn - use the tools you already know, now with more data. - -[Schedule a call with our Customer Success team now](https://meetings.hubspot.com/kyle-blount) to get started. - - -## How to start tracking shipments -There are many ways you can start tracking shipments with Terminal49. They all require that you have the Booking Number or Master Bill of Lading number for the shipments you want to track. - - - [Send an email with a CSV to track@terminal49.com](https://help.terminal49.com/en/articles/5506959-how-to-add-shipments-via-email) - - Upload a CSV through [our dashboard](https://app.terminal49.com/shipments/imports/new) - - Input shipments directly through [our dashboard](https://app.terminal49.com/shipments/imports/new) - - [Use the Terminal49 API to create TrackingRequests](/api-docs/api-reference/tracking-requests/create-a-tracking-request) - - -## Getting Started -Schedule your call now! - -For current Terminal49 customers, [schedule a call with our Customer Support team](https://meetings.hubspot.com/kyle-blount) we'll get you set up. - -If you're not yet a customer, [schedule a demo with our sales team](https://www.terminal49.com/contact) - they'll help you find the solution that's best for you. diff --git a/docs/datasync/supported-destinations.mdx b/docs/datasync/supported-destinations.mdx deleted file mode 100644 index ecaf2138..00000000 --- a/docs/datasync/supported-destinations.mdx +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Supported Destinations -og:title: Supported Destinations | Terminal49 API Documentation -og:description: Explore supported data destinations using Terminal49's API to sync container data efficiently. ---- -Terminal49 DataSync directly supports over a dozen different destinations out of the box. - -Tools like **Excel**, **Power BI**, and **many TMS and ERP systems** can read data from a database or data warehouse. We can feed data into your system and indirectly power those tools. - -Don’t see your supported database or tool? Please [reach out](https://www.terminal49.com/contact). - - -## Spreadsheets - - Google Sheets - -## Databases - - MariaDB - - Microsoft SQL Server - - MySQL - - Postgres - - SingleStore - -## Data Warehouses - - Amazon Athena - - Amazon Redshift - - Clickhouse - - Databricks - - Firebolt - - Google BigQuery - - Snowflake - -## Object Store - - Amazon S3 - - Azure Blob Store - - Cloudflare R2 - - Google Cloud Storage - -## Other Systems - -If you have something like **Excel**, **Power BI/Tableau**, or a **TMS** or **ERP** system, contact your IT team to see what database, data warehouse, or object store is powering them. We can [securely](https://help.terminal49.com/en/articles/7988732-security-considerations-for-terminal49-datasync) feed data into most systems. - - diff --git a/docs/datasync/table-properties/containers.mdx b/docs/datasync/table-properties/containers.mdx deleted file mode 100644 index c8636969..00000000 --- a/docs/datasync/table-properties/containers.mdx +++ /dev/null @@ -1,126 +0,0 @@ ---- -title: Containers (deprecated) -og:title: Container Table Properties | Terminal49 Datasync -og:description: Access detailed container table properties through Terminal49's Datasync API. ---- -_This is a deprecated version of the `containers` table, used by DataSync customers before September 2024._ - -The `containers` table contains 1 row per container (`container_id` is the unique key). - -Each container is part of 1 shipment (`shipment_id`). - -This is a large table with denormalized columns to make it easy to use for reporting purposes. - - -| COLUMN NAME | DESCRIPTION | TYPE | -|-----------------------------------|----------------------------------------------------------------------------------------------------------------|-------------| -| `container_id` | Container ID. This is the unique key of the table. | `text` | -| `container_number` | Container number | `text` | -| `shipment_id` | Shipment ID associated to the container | `text` | -| `shipment_bill_of_lading_number` | Shipment number from the tracking request | `text` | -| `shipment_normalized_number` | The normalized version of the shipment number used for querying the carrier | `text` | -| `shipment_reference_numbers` | Reference numbers of the shipment, concatenated | `text` | -| `container_reference_numbers` | Reference numbers of the container, concatenated | `text` | -| `shipment_tags` | Tags added to the shipment, sorted by alphabetical order, concatenated and separated by a comma | `text` | -| `customer_id` | Account ID of the customer | `text` | -| `customer_name` | Name of the customer | `text` | -| `shipping_line_scac` | Standard carrier alpha numeric code of the shipping line | `text` | -| `shipping_line_name` | Name of the shipping line | `text` | -| `origin_country_code` | Origin country code, populated only if the Empty Out event happens at a different location from the POL | `text` | -| `origin_locode` | Origin UN/LOCODE, populated only if the Empty Out event happens at a different location from the POL | `text` | -| `origin_city` | Origin city, populated only if the Empty Out event happens at a different location from the POL | `text` | -| `origin_timezone` | Origin time zone, populated only if the Empty Out event happens at a different location from the POL | `text` | -| `pol_country_code` | Port of Lading country code | `text` | -| `pol_locode` | Port of Lading UN/LOCODE | `text` | -| `pol_city` | Port of Lading city | `text` | -| `pol_timezone` | Port of Lading time zone | `text` | -| `pod_country_code` | Port of Discharge country code | `text` | -| `pod_locode` | Port of Discharge UN/LOCODE | `text` | -| `pod_city` | Port of Discharge city | `text` | -| `pod_timezone` | Port of Discharge time zone | `text` | -| `pod_terminal_firms_code` | Port of Discharge terminal firms code | `text` | -| `pod_terminal_nickname` | Port of Discharge terminal nickname | `text` | -| `pod_terminal_name` | Port of Discharge terminal name | `text` | -| `destination_country_code` | Destination country code | `text` | -| `destination_locode` | Destination UN/LOCODE | `text` | -| `destination_city` | Destination city | `text` | -| `destination_timezone` | Destination time zone | `text` | -| `destination_terminal_firms_code` | Destination terminal firms code | `text` | -| `destination_terminal_nickname` | Destination terminal nickname | `text` | -| `destination_terminal_name` | Destination terminal name | `text` | -| `pol_empty_out_at` | Empty Out, as a UTC timestamp | `timestamp` | -| `pol_empty_out_at_local` | Empty Out, as a string in the POL local time zone | `text` | -| `pol_full_in_at` | Full In event, as a UTC timestamp | `timestamp` | -| `pol_full_in_at_local` | Full In event, as a string in the POL local time zone | `text` | -| `origin_rail_loaded_at` | Origin Rail Loaded, as a UTC timestamp | `timestamp` | -| `origin_rail_loaded_at_local` | Origin Rail Loaded, as a string in the origin local time zone | `text` | -| `origin_rail_departed_at` | Origin Rail Departed, as a UTC timestamp | `timestamp` | -| `origin_rail_departed_at_local` | Origin Rail Departed, as a string in the origin local time zone | `text` | -| `pol_rail_arrived_at` | Port of Lading Rail Arrived, as a UTC timestamp | `timestamp` | -| `pol_rail_arrived_at_local` | Port of Lading Rail Arrived, as a string in the origin local time zone | `text` | -| `pol_rail_unloaded_at` | Port of Lading Rail Unloaded, as a UTC timestamp | `timestamp` | -| `pol_rail_unloaded_at_local` | Port of Lading Rail Unloaded, as a string in the origin local time zone | `text` | -| `pol_loaded_at` | Port of Lading Loaded event, as a UTC timestamp | `timestamp` | -| `pol_loaded_at_local` | Port of Lading Loaded event, as a string in the POL local time zone | `text` | -| `pol_etd_at` | Port of Lading Estimated Time of Departure, as a UTC timestamp | `timestamp` | -| `pol_etd_at_local` | Port of Lading Estimated Time of Departure, as a string in the POL local time zone | `text` | -| `pol_atd_at` | Port of Lading Actual Time of Departure, as a UTC timestamp | `timestamp` | -| `pol_atd_at_local` | Port of Lading Actual Time of Departure, as a string in the POL local time zone | `text` | -| `pod_eta_at` | Port of Discharge Estimated Time of Arrival, as a UTC timestamp | `timestamp` | -| `pod_eta_at_local` | Port of Discharge Estimated Time of Arrival, as a string in the POD local time zone | `text` | -| `pod_arrived_at` | Port of Discharge Actual Time of Arrival, as a UTC timestamp | `timestamp` | -| `pod_arrived_at_local` | Port of Discharge Actual Time of Arrival, as a string in the POD local time zone | `text` | -| `pod_berthed_at` | Port of Discharge Berthed event, as a UTC timestamp | `timestamp` | -| `pod_berthed_at_local` | Port of Discharge Berthed event, as a string in the POD local time zone | `text` | -| `pod_discharged_at` | Port of Discharge Discharged event, as a UTC timestamp | `timestamp` | -| `pod_discharged_at_local` | Port of Discharge Discharged event, as a string in the POD local time zone | `text` | -| `pod_last_free_day_on` | Current Last Free Day at the POD terminal, as a UTC timestamp | `timestamp` | -| `pod_last_free_day_on_local` | Current Last Free Day at the POD terminal, as a string in the POD local time zone | `text` | -| `pod_pickup_appointment_at` | Port of Discharge Pickup Appointment, as a UTC timestamp | `timestamp` | -| `pod_pickup_appointment_at_local` | Port of Discharge Pickup Appointment, as a string in the POD local time zone | `text` | -| `pod_full_out_at` | Port of Discharge Full Out event, as a UTC timestamp | `timestamp` | -| `pod_full_out_at_local` | Port of Discharge Full Out event, as a string in the POD local time zone | `text` | -| `rail_loaded_at` | First rail loaded after the POD discharge, as a UTC timestamp | `timestamp` | -| `rail_loaded_at_local` | First rail loaded after the POD discharge, as a string in the POD local time zone | `text` | -| `rail_departed_at` | First rail departure after the POD discharge, as a UTC timestamp | `timestamp` | -| `rail_departed_at_local` | First rail departure after the POD discharge, as a string in the POD local time zone | `text` | -| `destination_eta_at` | Destination Estimated Time of Arrival, as a UTC timestamp | `timestamp` | -| `destination_eta_at_local` | Destination Estimated Time of Arrival, as a string in the Destination local time zone | `text` | -| `destination_arrived_at` | Destination Actual Time of Arrival, as a UTC timestamp | `timestamp` | -| `destination_arrived_at_local` | Destination Actual Time of Arrival, as a string in the Destination local time zone | `text` | -| `rail_unloaded_at` | Destination Rail Unloaded, as a UTC timestamp | `timestamp` | -| `rail_unloaded_at_local` | Destination Rail Unloaded, as a string in the Destination local time zone | `text` | -| `destination_full_out_at` | Destination Full Out event, as a UTC timestamp | `timestamp` | -| `destination_full_out_at_local` | Destination Full Out event, as a string in the Destination local time zone | `text` | -| `empty_terminated_at` | Container Empty Returned event, as a UTC timestamp | `timestamp` | -| `empty_terminated_at_local` | Container Empty Returned event, as a string in the POD local time zone | `text` | -| `fees_at_pod_terminal` | Current fee amounts, in JSON format | `text` | -| `demurrage_at_pod_terminal` | Current demurrage amount owed | `text` | -| `holds_at_pod_terminal` | Current terminal hold statuses, in JSON format | `text` | -| `freight_hold_at_pod_terminal` | Current freight hold, value is either Hold or empty | `text` | -| `customs_hold_at_pod_terminal` | Current customs hold, value is either Hold or empty | `text` | -| `usda_hold_at_pod_terminal` | Current USDA hold, value is either Hold or empty | `text` | -| `tmf_hold_at_pod_terminal` | Current Traffic Mitigation Fee hold, value is either Hold or empty | `text` | -| `other_hold_at_pod_terminal` | Any other current hold, value is either Hold or empty | `text` | -| `location_at_pod_terminal` | Location at port of discharge terminal | `text` | -| `availability_known` | Yes if Terminal49 is receiving availability status from the terminal, No otherwise. | `text` | -| `available_for_pickup` | If availability_known is Yes, then Yes if the container is available to be picked up at terminal, No otherwise | `text` | -| `equipment_length` | Length of the container | `integer` | -| `equipment_type` | Container type: Dry, Flat Rack, Open Top, Reefer, Tank, unknown | `text` | -| `equipment_height` | Container height: High Cube, Standard, unknown | `text` | -| `equipment` | Concatenation of the equipment_length, equipment_type, and equipment_height | `text` | -| `weight_in_lbs` | Weight of the containre in lbs | `integer` | -| `seal_number` | Seal number of the container | `text` | -| `pod_full_out_chassis_number` | The chassis number used when container was picked up at POD, if available | `text` | -| `pol_voyage_number` | Voyage number of the vessel that departed or will depart from the POL | `text` | -| `pol_vessel_name` | Name of the vessel that departed or will depart from the POL | `text` | -| `pol_vessel_imo` | IMO of the vessel that departed or will depart from the POL | `text` | -| `pod_voyage_number` | Voyage number of the vessel that arrived or will arrive at the POD | `text` | -| `pod_vessel_name` | Name of the vessel that arrived or will arrive at the POD | `text` | -| `pod_vessel_imo` | IMO of the vessel that arrived or will arrive at the POD | `text` | -| `terminal_checked_at` | When the terminal was last checked, as a UTC timestamp | `timestamp` | -| `line_tracking_last_succeeded_at` | When the shipment information was last refreshed from the shipping line, as a UTC timestamp | `timestamp` | -| `line_tracking_stopped_at` | When the tracking of the container stopped, as a UTC timestamp | `timestamp` | -| `line_tracking_stopped_reason` | The reason Terminal49 stopped the tracking | `text` | -| `created_at` | When the container was added, as a UTC timestamp | `timestamp` | -| `updated_at` | When the container was last updated, as a UTC timestamp | `timestamp` | diff --git a/docs/datasync/table-properties/containers_rail.mdx b/docs/datasync/table-properties/containers_rail.mdx deleted file mode 100644 index ad4a1749..00000000 --- a/docs/datasync/table-properties/containers_rail.mdx +++ /dev/null @@ -1,118 +0,0 @@ ---- -title: Containers -og:title: Rail Container Table Properties | Terminal49 Datasync -og:description: Get information on rail container table properties via Terminal49's Datasync API. ---- -The `containers` table contains 1 row per container (`container_id` is the unique key). - -Each container is part of 1 shipment (`shipment_id`). - -This is a large table with denormalized columns to make it easy to use for reporting purposes. - -For each **event timestamp** there are 2 columns : -- a `timestamp` type column in the UTC time zone (Universal Time Coordinated), e.g., `pol_loaded_at`. -- a `text` type column in the local time zone of where the event happened, e.g., `pol_loaded_at_local`. The format of the text is : `YYYY-MM-DD HH:MI:SS`. For example `2024-09-24 17:25:00` for 5:25 PM on September 24, 2024. Depending on the event, the time zone applied can be the one from the Port of Lading (`pol_timezone`), the Port of Discharge (`pod_timezone`), or the Inland Destination (`ind_timezone`). - -_Columns marked with * are only included with the Intermodal Rail product._ - -| COLUMN NAME | DESCRIPTION | TYPE | -|-----------------------------------|------------------------------------------------------------------------------------------------------------------------|------------| -| `container_id` | Container ID. This is the unique key of the table. | `text` | -| `container_number` | Container number | `text` | -| `shipment_id` | Shipment ID associated to the container | `text` | -| `shipment_bill_of_lading_number` | Shipment number from the tracking request | `text` | -| `shipment_normalized_number` | The normalized version of the shipment number used for querying the carrier | `text` | -| `shipment_reference_numbers` | Reference numbers of the shipment, concatenated | `text` | -| `container_reference_numbers` | Reference numbers of the container, concatenated | `text` | -| `shipment_tags` | Tags added to the shipment, sorted by alphabetical order, concatenated and separated by a comma | `text` | -| `customer_id` | Account ID of the customer | `text` | -| `customer_name` | Name of the customer | `text` | -| `shipping_line_scac` | Standard carrier alpha numeric code of the shipping line | `text` | -| `shipping_line_name` | Name of the shipping line | `text` | -| `pol_country_code` | Port of Lading country code | `text` | -| `pol_locode` | Port of Lading UN/LOCODE | `text` | -| `pol_city` | Port of Lading city | `text` | -| `pol_timezone` | Port of Lading time zone | `text` | -| `pod_country_code` | Port of Discharge country code | `text` | -| `pod_locode` | Port of Discharge UN/LOCODE | `text` | -| `pod_city` | Port of Discharge city | `text` | -| `pod_timezone` | Port of Discharge time zone | `text` | -| `pod_terminal_firms_code` | Port of Discharge terminal firms code | `text` | -| `pod_terminal_nickname` | Port of Discharge terminal nickname | `text` | -| `pod_terminal_name` | Port of Discharge terminal name | `text` | -| `ind_country_code` | Inland Destination country code | `text` | -| `ind_locode` | Inland Destination UN/LOCODE | `text` | -| `ind_city` | Inland Destination city | `text` | -| `ind_timezone` | Inland Destination time zone | `text` | -| `ind_terminal_firms_code` | Inland Destination terminal firms code | `text` | -| `ind_terminal_nickname` | Inland Destination terminal nickname | `text` | -| `ind_terminal_name` | Inland Destination terminal name | `text` | -| `empty_out_at` | Empty Out, as a UTC timestamp | `timestamp`| -| `empty_out_at_local` | Empty Out, as a string in the POL local time zone | `text` | -| `full_in_at` | Full In event, as a UTC timestamp | `timestamp`| -| `full_in_at_local` | Full In event, as a string in the POL local time zone | `text` | -| `pol_loaded_at` | Port of Lading Loaded event, as a UTC timestamp | `timestamp`| -| `pol_loaded_at_local` | Port of Lading Loaded event, as a string in the POL local time zone | `text` | -| `pol_etd_at` | Port of Lading Estimated Time of Departure, as a UTC timestamp | `timestamp`| -| `pol_etd_at_local` | Port of Lading Estimated Time of Departure, as a string in the POL local time zone | `text` | -| `pol_atd_at` | Port of Lading Actual Time of Departure, as a UTC timestamp | `timestamp`| -| `pol_atd_at_local` | Port of Lading Actual Time of Departure, as a string in the POL local time zone | `text` | -| `pod_eta_at` | Port of Discharge Estimated Time of Arrival, as a UTC timestamp | `timestamp`| -| `pod_eta_at_local` | Port of Discharge Estimated Time of Arrival, as a string in the POD local time zone | `text` | -| `pod_arrived_at` | Port of Discharge Actual Time of Arrival, as a UTC timestamp | `timestamp`| -| `pod_arrived_at_local` | Port of Discharge Actual Time of Arrival, as a string in the POD local time zone | `text` | -| `pod_berthed_at` | Port of Discharge Berthed event, as a UTC timestamp | `timestamp`| -| `pod_berthed_at_local` | Port of Discharge Berthed event, as a string in the POD local time zone | `text` | -| `pod_discharged_at` | Port of Discharge Discharged event, as a UTC timestamp | `timestamp`| -| `pod_discharged_at_local` | Port of Discharge Discharged event, as a string in the POD local time zone | `text` | -| `pod_last_free_day_on` | Current Last Free Day at the POD terminal, as a UTC timestamp

Named `pickup_lfd` in the API | `timestamp`| -| `pod_last_free_day_on_local` | Current Last Free Day at the POD terminal, as a string in the POD local time zone | `text` | -| `pod_pickup_appointment_at` | Port of Discharge Pickup Appointment, as a UTC timestamp

Named `pickup_appointment_at` in the API | `timestamp`| -| `pod_pickup_appointment_at_local` | Port of Discharge Pickup Appointment, as a string in the POD local time zone | `text` | -| `pod_full_out_at` | Port of Discharge Full Out event, as a UTC timestamp | `timestamp`| -| `pod_full_out_at_local` | Port of Discharge Full Out event, as a string in the POD local time zone | `text` | -| `pod_rail_carrier_scac`* | SCAC of the rail carrier at the POD | `text` | -| `pod_rail_loaded_at`* | First rail loaded after the POD discharge, as a UTC timestamp | `timestamp`| -| `pod_rail_loaded_at_local`* | First rail loaded after the POD discharge, as a string in the POD local time zone | `text` | -| `pod_rail_departed_at`* | First rail departure after the POD discharge, as a UTC timestamp | `timestamp`| -| `pod_rail_departed_at_local`* | First rail departure after the POD discharge, as a string in the POD local time zone | `text` | -| `ind_rail_carrier_scac`* | SCAC of the rail carrier at the inland destination | `text` | -| `ind_eta_at`* | Inland Destination Estimated Time of Arrival, as a UTC timestamp | `timestamp`| -| `ind_eta_at_local`* | Inland Destination Estimated Time of Arrival, as a string in the Inland Destination local time zone | `text` | -| `ind_arrived_at`* | Inland Destination Actual Time of Arrival, as a UTC timestamp

Named `ind_ata_at` in the API | `timestamp`| -| `ind_arrived_at_local`* | Inland Destination Actual Time of Arrival, as a string in the Inland Destination local time zone | `text` | -| `ind_rail_unloaded_at`* | Inland Destination Rail Unloaded, as a UTC timestamp | `timestamp`| -| `ind_rail_unloaded_at_local`* | Inland Destination Rail Unloaded, as a string in the Inland Destination local time zone | `text` | -| `ind_last_free_day_on`* | Last Free Day at the inland destination facility, as a UTC timestamp

Named `ind_facility_lfd_on` in the API | `timestamp`| -| `ind_last_free_day_on_local`* | Last Free Day at the inland destination facility, as a string in the inland estination local time zone | `text` | -| `ind_full_out_at` | Inland Destination Full Out event, as a UTC timestamp

Named `final_destination_full_out_at` in the API | `timestamp`| -| `ind_full_out_at_local` | Inland Destination Full Out event, as a string in the Inland Destination local time zone | `text` | -| `empty_terminated_at` | Container Empty Returned event, as a UTC timestamp | `timestamp`| -| `empty_terminated_at_local` | Container Empty Returned event, as a string in the POD local time zone | `text` | -| `fees_at_pod_terminal` | Current fee amounts at the POD terminal, in JSON format | `text` | -| `demurrage_at_pod_terminal` | Current demurrage amount owed at the POD terminal | `text` | -| `holds_at_pod_terminal` | Current terminal hold statuses at the POD, in JSON format | `text` | -| `freight_hold_at_pod_terminal` | Current freight hold at the POD terminal, value is either "Hold" or empty | `text` | -| `customs_hold_at_pod_terminal` | Current customs hold at the POD terminal, value is either "Hold" or empty | `text` | -| `usda_hold_at_pod_terminal` | Current USDA hold at the POD terminal, value is either "Hold" or empty | `text` | -| `tmf_hold_at_pod_terminal` | Current Traffic Mitigation Fee hold at the POD terminal, value is either "Hold" or empty | `text` | -| `other_hold_at_pod_terminal` | Any other current hold at the POD terminal, value is either "Hold" or empty | `text` | -| `location_at_pod_terminal` | Location at the port of discharge terminal | `text` | -| `availability_known` | Yes if Terminal49 is receiving availability status from the POD terminal, No otherwise. | `text` | -| `available_for_pickup` | If availability_known is Yes, then Yes if the container is available to be picked up at the POD terminal, No otherwise | `text` | -| `equipment_length` | Length of the container | `integer` | -| `equipment_type` | Container type: Dry, Flat Rack, Open Top, Reefer, Tank, unknown | `text` | -| `equipment_height` | Container height: High Cube, Standard, unknown | `text` | -| `equipment` | Concatenation of the equipment_length, equipment_type, and equipment_height | `text` | -| `weight_in_lbs` | Weight of the containre in lbs | `integer` | -| `seal_number` | Seal number of the container | `text` | -| `pod_full_out_chassis_number` | The chassis number used when container was picked up at POD, if available | `text` | -| `pod_voyage_number` | Voyage number of the vessel that arrived or will arrive at the POD | `text` | -| `pod_vessel_name` | Name of the vessel that arrived or will arrive at the POD | `text` | -| `pod_vessel_imo` | IMO of the vessel that arrived or will arrive at the POD | `text` | -| `terminal_checked_at` | When the POD terminal was last checked, as a UTC timestamp | `timestamp`| -| `line_tracking_last_succeeded_at` | When the shipment information was last refreshed from the shipping line, as a UTC timestamp | `timestamp`| -| `line_tracking_stopped_at` | When the tracking of the container stopped, as a UTC timestamp | `timestamp`| -| `line_tracking_stopped_reason` | The reason Terminal49 stopped the tracking | `text` | -| `created_at` | When the container was added, as a UTC timestamp | `timestamp`| -| `updated_at` | When the container was last updated, as a UTC timestamp | `timestamp`| diff --git a/docs/datasync/table-properties/shipments.mdx b/docs/datasync/table-properties/shipments.mdx deleted file mode 100644 index 7e13f8fd..00000000 --- a/docs/datasync/table-properties/shipments.mdx +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Shipments -og:title: Shipment Table Properties | Terminal49 Datasync -og:description: Retrieve shipment table properties with the Datasync feature from Terminal49 for detailed tracking. ---- -The `shipments` table contains 1 row per shipment (`shipment_id` is the unique key). - -A shipment contains 1 or more containers. - - -| COLUMN NAME | DESCRIPTION | TYPE | -|-----------------------------------|-------------------------------------------------------------------------------------------------|-------------| -| `shipment_id` | Shipment ID. This is the unique key of the table. | `text` | -| `shipping_line_scac` | Standard carrier alpha numeric code of the shipping line | `text` | -| `shipping_line_name` | Name of the shipping line | `text` | -| `bill_of_lading_number` | Shipment number from the tracking request | `text` | -| `normalized_number` | The normalized version of the shipment number used for querying the carrier | `text` | -| `reference_numbers` | Reference numbers of the shipment, contatenated | `text` | -| `tags` | Tags added to the shipment, sorted by alphabetical order, concatenated and separated by a comma | `text` | -| `customer_id` | Account ID of the customer | `text` | -| `customer_name` | Name of the customer | `text` | -| `pol_locode` | Port of Lading UN/LOCODE | `text` | -| `pod_locode` | Port of Discharge UN/LOCODE | `text` | -| `pod_terminal_firms_code` | Port of Discharge terminal firms code | `text` | -| `destination_locode` | Destination UN/LOCODE | `text` | -| `destination_terminal_firms_code` | Destination terminal firms code | `text` | -| `pol_atd_at` | Port of Lading Actual Time of Departure, as a UTC timestamp | `timestamp` | -| `pol_etd_at` | Port of Lading Estimated Time of Departure, as a UTC timestamp | `timestamp` | -| `pod_eta_at` | Port of Discharge Estimated Time of Arrival, as a UTC timestamp | `timestamp` | -| `pod_arrived_at` | Port of Discharge Actual Time of Arrival, as a UTC timestamp | `timestamp` | -| `pod_voyage_number` | Voyage number of the vessel that arrived or will arrive at the POD | `text` | -| `pod_vessel_name` | Name of the vessel that arrived or will arrive at the POD | `text` | -| `pod_vessel_imo` | IMO of the vessel that arrived or will arrive at the POD | `text` | -| `line_tracking_last_succeeded_at` | When the shipment information was last refreshed from the shipping line, as a UTC timestamp | `timestamp` | -| `line_tracking_stopped_at` | When the tracking of the shipment stopped, as a UTC timestamp | `timestamp` | -| `line_tracking_stopped_reason` | Reason why the tracking of the shipment stopped | `text` | -| `created_at` | When the shipment was added, as a UTC timestamp | `timestamp` | -| `updated_at` | When the shipment was last updated, as a UTC timestamp | `timestamp` | \ No newline at end of file diff --git a/docs/datasync/table-properties/tracking-requests.mdx b/docs/datasync/table-properties/tracking-requests.mdx deleted file mode 100644 index 9e1cea2a..00000000 --- a/docs/datasync/table-properties/tracking-requests.mdx +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Tracking Requests -og:title: Tracking Request Table Properties | Terminal49 Datasync -og:description: View tracking request table properties with Terminal49's Datasync to manage shipment tracking. ---- -The `tracking_requests` table contains 1 row per tracking request (`tracking_request_id`is the unique key). - -A tracking request can fail or succeed (`status` column). A successful tracking request will lead to the creation of a shipment (`shipment_id`). - -There can be multiple tracking requests for the same requested number (possibly failing before finally succeeding). - -| COLUMN NAME | DESCRIPTION | TYPE | -|-----------------------|-----------------------------------------------------------------------------------------------|-------------| -| `tracking_request_id` | Tracking request ID. This is the unique key of the table. | `text` | -| `request_number` | Number requested to be tracked | `text` | -| `reference_numbers` | Reference numbers associated to the tracking request, concatenated | `text` | -| `shipment_tags` | Tags added to the request, concatenated and separated by a comma | `text` | -| `status` | Status of the tracking request: created, pending, awaiting_manifest, failed, tracking_stopped | `text` | -| `failed_reason` | For tracking requests that failed, a description of the error | `text` | -| `request_type` | Type of tracking request: bill_of_lading, booking_number, or container | `text` | -| `scac` | Standard carrier alpha numeric code of the shipping line | `text` | -| `shipment_id` | If the tracking request succeeded, this is the ID of the shipment that was created | `text` | -| `created_at` | When the tracking was requested, as a UTC timestamp | `timestamp` | -| `updated_at` | When the tracking request was last updated, as a UTC timestamp | `timestamp` | \ No newline at end of file diff --git a/docs/datasync/table-properties/transfer-status.mdx b/docs/datasync/table-properties/transfer-status.mdx deleted file mode 100644 index 1ee95756..00000000 --- a/docs/datasync/table-properties/transfer-status.mdx +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: Transfer Status -og:title: Transfer Status Table Properties | Terminal49 Datasync -og:description: Access transfer status table properties for seamless data synchronization using Terminal49. ---- -The `_transfer_status` is an additional technical table that identifies when each table was last updated by DataSync. - -| COLUMN NAME | DESCRIPTION | TYPE | -|----------------------------|---------------------------------------------------|-------------| -| `data_model_name` | Name of the table | `text` | -| `transfer_last_updated_at` | When the latest sync happened, as a UTC timestamp | `timestamp` | \ No newline at end of file diff --git a/docs/datasync/table-properties/transport-events.mdx b/docs/datasync/table-properties/transport-events.mdx deleted file mode 100644 index 325bd49f..00000000 --- a/docs/datasync/table-properties/transport-events.mdx +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: Transport Events -og:title: Transport Events Table Properties | Terminal49 Datasync -og:description: View transport events table properties with Terminal49's Datasync to manage shipment tracking. ---- -The `transport_events` table contains 1 row per event (`id`is the unique key). - -An event is associated to a specific container (`container_id` is the foreign key). - -An event is a specific milestone in the container lifecycle: for example, when the container was loaded at the Port of Lading, or when the vessel arrived at the Port of Discharge. - -These events are provided as columns in the `containers` DataSync table, and as rows here in the `transport_events` table. You can use one or the other based on what is most practical for you. - -The `transport_events` table includes the transshipment events, which are not part of the `containers` table columns. - -This table does not provide any estimated future events. - -_The `transport_events` table is currently only provided to DataSync customers who request it._ - -_Rail events from the POD to the inland destination are only provided in the Intermodal Rail product : rail_loaded, rail_departed, rail_arrived, arrived_at_inland_destination, rail_unloaded, pickup_lfd.changed._ - -| COLUMN NAME | DESCRIPTION | TYPE | -|---------------------------|-----------------------------------------------------------------------------------------------|-------------| -| `id` | Transport Event ID. This is the unique key of the table. | `text` | -| `event` | Name of the transport event. For example: container.transport.vessel_departed | `text` | -| `event_timestamp` | When the event happened, as a UTC timestamp | `timestamp` | -| `event_timestamp_local` | When the event happened, as a string in the local time zone | `text` | -| `container_id` | ID of the container the event is associated to | `text` | -| `container_number` | Number of the container the event is associated to | `text` | -| `shipment_id` | ID of the shipment the event is associated to | `text` | -| `shipment_number` | Number of the shipment the event is associated to | `text` | -| `port_metro_id` | ID of the location where the event happened | `text` | -| `port_metro_locode` | Locode of the location where the event happened | `text` | -| `port_metro_country_code` | Country code of the location where the event happened | `text` | -| `port_metro_city` | Name of the location where the event happened | `text` | -| `port_metro_time_zone` | Name of the time zone where the event happened | `text` | -| `facility_id` | ID of the facility (terminal) where the event happened | `text` | -| `facility_firms_code` | Firms code of the facility (terminal) where the event happened | `text` | -| `facility_nickname` | Nickname of the facility (terminal) where the event happened | `text` | -| `facility_name` | Name of the facility (terminal) where the event happened | `text` | -| `vessel_id` | ID of the vessel associated to the event | `text` | -| `vessel_name` | Name of the vessel associated to the event | `text` | -| `vessel_imo` | IMO of the vessel associated to the event | `text` | -| `vessel_mmsi` | MMSI of the vessel associated to the event | `text` | -| `voyage_number` | Voyage number associated to the event | `text` | -| `data_source_label` | Data source of the event: shipping_line, terminal, ais, rail, t49_operations_team, user_input | `text` | -| `invalidated_at` | When the event was marked as invalid, as a UTC timestamp | `timestamp` | -| `invalidation_reason` | Reason why the event was marked as invalid | `text` | -| `previous_version_id` | If the event replaces an invalidated event, this is the ID of the invalidated event | `text` | -| `created_at` | When the event was originally added, as a UTC timestamp | `timestamp` | -| `updated_at` | When the event was updated, as a UTC timestamp | `timestamp` | diff --git a/docs/home.mdx b/docs/home.mdx deleted file mode 100644 index 186db702..00000000 --- a/docs/home.mdx +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Terminal49 Dev Documentation -og:title: Terminal49 Home | API Documentation -og:description: Learn about Terminal49's home features and API documentation for container and shipment tracking. ---- -We offer two fantastic ways to track your shipments from origin to destination. - - 1. [Terminal49 DataSync](/datasync/overview). Get tables full of fresh information delivered into your current data system. Easy to set up, and perfect for complementing your current data. - 2. [Terminal49 API](/api-docs/getting-started/start-here). Connect directly with the API, pull data for specific shipments and containers, and get updates via webhooks. - -If you already have a data store that feeds the rest of your system, DataSync is probably what you want. - -## What can I use Terminal49 data for? -Here are just a few of the data points we return and possible use-cases. - -| DATA | EXAMPLE USE CASE | -|----------------------------------------|--------------------------------------------------------------------------| -| Destination ETA | Surface ETA changes to your relevant teams as they're reported | -| Last Free Day and terminal status¹ | Track containers approaching LFD and prioritize dispatching | -| Fees and holds at destination terminal | Clear your cargo to keep you containers moving | -| Actual departure and arrival times | Report journey times by route to compare your ocean carriers performance | - -_1. At container ports in the US_ - -## How it works -All you need to provide are your BOL numbers and SCACs. Terminal 49 will lookup the shipment with the carrier and populate shipment details including containers. - -Once the shipment is set up, Terminal 49 periodically checks with the carrier and the destination terminal. - -If any of the details of your shipment or containers change (for example - if the ETA changes) we'll ensure you're always kept up to date. - - - If you're using DataSync, we'll update the data in your system - - If you're using the API, we'll post the shipment to the the webhook you provide - -👈🏽 Please click API Docs or Data Sync on the left to get started! \ No newline at end of file diff --git a/docs/images/1567356d-03eb-4938-b23f-493aa8b9fdf8.png b/docs/images/1567356d-03eb-4938-b23f-493aa8b9fdf8.png deleted file mode 100644 index c4b4d0ad..00000000 Binary files a/docs/images/1567356d-03eb-4938-b23f-493aa8b9fdf8.png and /dev/null differ diff --git a/docs/images/1_route_location_ports.png b/docs/images/1_route_location_ports.png deleted file mode 100644 index 9b2a0450..00000000 Binary files a/docs/images/1_route_location_ports.png and /dev/null differ diff --git a/docs/images/2_vessel_positions_between_timeframe.png b/docs/images/2_vessel_positions_between_timeframe.png deleted file mode 100644 index d4a7c87a..00000000 Binary files a/docs/images/2_vessel_positions_between_timeframe.png and /dev/null differ diff --git a/docs/images/2b1837f2-071c-4b0f-aaf4-c90409252ef0.png b/docs/images/2b1837f2-071c-4b0f-aaf4-c90409252ef0.png deleted file mode 100644 index 3eb1be96..00000000 Binary files a/docs/images/2b1837f2-071c-4b0f-aaf4-c90409252ef0.png and /dev/null differ diff --git a/docs/images/3_port_route_with_coords.png b/docs/images/3_port_route_with_coords.png deleted file mode 100644 index f2970590..00000000 Binary files a/docs/images/3_port_route_with_coords.png and /dev/null differ diff --git a/docs/images/4_port_route.png b/docs/images/4_port_route.png deleted file mode 100644 index 846b6e29..00000000 Binary files a/docs/images/4_port_route.png and /dev/null differ diff --git a/docs/images/7c1e91ee-25fa-4114-b9a8-c062fb0de1f1.png b/docs/images/7c1e91ee-25fa-4114-b9a8-c062fb0de1f1.png deleted file mode 100644 index 0621cc46..00000000 Binary files a/docs/images/7c1e91ee-25fa-4114-b9a8-c062fb0de1f1.png and /dev/null differ diff --git a/docs/images/a8555d20-e23f-459e-b894-1027adbecdf7.jpeg b/docs/images/a8555d20-e23f-459e-b894-1027adbecdf7.jpeg deleted file mode 100644 index f01d7b56..00000000 Binary files a/docs/images/a8555d20-e23f-459e-b894-1027adbecdf7.jpeg and /dev/null differ diff --git a/docs/images/carriers_screenshot.png b/docs/images/carriers_screenshot.png deleted file mode 100644 index 94f49141..00000000 Binary files a/docs/images/carriers_screenshot.png and /dev/null differ diff --git a/docs/images/create-shipment-flow.png b/docs/images/create-shipment-flow.png deleted file mode 100644 index e06142e6..00000000 Binary files a/docs/images/create-shipment-flow.png and /dev/null differ diff --git a/docs/images/new_webhook.png b/docs/images/new_webhook.png deleted file mode 100644 index c94341de..00000000 Binary files a/docs/images/new_webhook.png and /dev/null differ diff --git a/docs/images/terminal49-container-tracking-widget.png b/docs/images/terminal49-container-tracking-widget.png deleted file mode 100644 index f01d7b56..00000000 Binary files a/docs/images/terminal49-container-tracking-widget.png and /dev/null differ diff --git a/docs/images/terminal49-map-colors.png b/docs/images/terminal49-map-colors.png deleted file mode 100644 index c4b4d0ad..00000000 Binary files a/docs/images/terminal49-map-colors.png and /dev/null differ diff --git a/docs/images/terminal49-map-expanded.png b/docs/images/terminal49-map-expanded.png deleted file mode 100644 index 0621cc46..00000000 Binary files a/docs/images/terminal49-map-expanded.png and /dev/null differ diff --git a/docs/images/terminal49-map.png b/docs/images/terminal49-map.png deleted file mode 100644 index 3eb1be96..00000000 Binary files a/docs/images/terminal49-map.png and /dev/null differ diff --git a/docs/logos/dark.svg b/docs/logos/dark.svg deleted file mode 100644 index 2f808838..00000000 --- a/docs/logos/dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/logos/favicon.svg b/docs/logos/favicon.svg deleted file mode 100644 index f0ce9fdc..00000000 --- a/docs/logos/favicon.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/docs/logos/light.svg b/docs/logos/light.svg deleted file mode 100644 index 2f808838..00000000 --- a/docs/logos/light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/mint.json b/docs/mint.json deleted file mode 100644 index eaeb1e4d..00000000 --- a/docs/mint.json +++ /dev/null @@ -1,215 +0,0 @@ -{ - "name": "Terminal 49", - "logo": "/logos/light.svg", - "favicon": "/logos/favicon.svg", - "colors": { - "primary": "#00A2FF", - "light": "#52bfff", - "dark": "#00A2FF" - }, - "modeToggle": { - "default": "light", - "isHidden": false - }, - "feedback": { - "thumbsRating": true, - "raiseIssue": true, - "suggestEdit": true - }, - "topbarCtaButton": { - "name": "Sign in", - "url": "https://app.terminal49.com/" - }, - "topbarLinks": [ - { - "name": "Start your free trial", - "url": "https://app.terminal49.com/register" - } - ], - "anchors": [ - { - "name": "Contact", - "icon": "phone", - "url": "https://www.terminal49.com/contact/" - }, - { - "name": "Schedule Demo", - "icon": "calendar-days", - "url": "https://www.terminal49.com/demo/" - }, - { - "name": "Blog", - "icon": "newspaper", - "url": "https://www.terminal49.com/blog/" - } - ], - "primaryTab": { - "name": "API Docs" - }, - "tabs": [ - { - "name": "DataSync", - "url": "datasync" - } - ], - "navigation": [ - { - "group": " ", - "pages": ["home"] - }, - { - "group": "Getting Started", - "pages": [ - "api-docs/getting-started/start-here", - "api-docs/getting-started/tracking-shipments-and-containers", - "api-docs/getting-started/list-shipments-and-containers", - "api-docs/getting-started/receive-status-updates" - ] - }, - { - "group": "In Depth Guides", - "pages": [ - "api-docs/in-depth-guides/quickstart", - "api-docs/in-depth-guides/webhooks", - "api-docs/in-depth-guides/including-resources", - "api-docs/in-depth-guides/adding-customer", - "api-docs/in-depth-guides/tracking-request-lifecycle", - "api-docs/in-depth-guides/event-timestamps", - "api-docs/in-depth-guides/terminal49-map", - "api-docs/in-depth-guides/terminal49-widget", - "api-docs/in-depth-guides/rail-integration-guide", - "api-docs/in-depth-guides/routing" - ] - }, - { - "group": "Useful Info", - "pages": [ - "api-docs/useful-info/api-data-sources-availability", - "api-docs/useful-info/pricing", - "api-docs/useful-info/test-numbers", - "api-docs/useful-info/tracking-request-retrying", - "api-docs/useful-info/webhook-events-examples" - ] - }, - { - "group": " ", - "pages": ["datasync/home"] - }, - { - "group": " ", - "pages": ["datasync/overview", "datasync/supported-destinations"] - }, - { - "group": "Table Properties", - "pages": [ - "datasync/table-properties/containers_rail", - "datasync/table-properties/shipments", - "datasync/table-properties/tracking-requests", - "datasync/table-properties/transport-events", - "datasync/table-properties/transfer-status", - "datasync/table-properties/containers" - ] - }, - { - "group": "Shipments", - "pages": [ - "api-docs/api-reference/shipments/list-shipments", - "api-docs/api-reference/shipments/get-a-shipment", - "api-docs/api-reference/shipments/edit-a-shipment", - "api-docs/api-reference/shipments/stop-tracking-shipment", - "api-docs/api-reference/shipments/resume-tracking-shipment" - ] - }, - { - "group": "Tracking Requests", - "pages": [ - "api-docs/api-reference/tracking-requests/list-tracking-requests", - "api-docs/api-reference/tracking-requests/create-a-tracking-request", - "api-docs/api-reference/tracking-requests/get-a-single-tracking-request", - "api-docs/api-reference/tracking-requests/edit-a-tracking-request" - ] - }, - { - "group": "Webhooks", - "pages": [ - "api-docs/api-reference/webhooks/get-single-webhook", - "api-docs/api-reference/webhooks/delete-a-webhook", - "api-docs/api-reference/webhooks/edit-a-webhook", - "api-docs/api-reference/webhooks/list-webhooks", - "api-docs/api-reference/webhooks/create-a-webhook", - "api-docs/api-reference/webhooks/list-webhook-ips" - ] - }, - { - "group": "Webhook Notifications", - "pages": [ - "api-docs/api-reference/webhook-notifications/get-a-single-webhook-notification", - "api-docs/api-reference/webhook-notifications/list-webhook-notifications", - "api-docs/api-reference/webhook-notifications/get-webhook-notification-payload-examples" - ] - }, - { - "group": "Containers", - "pages": [ - "api-docs/api-reference/containers/list-containers", - "api-docs/api-reference/containers/edit-a-container", - "api-docs/api-reference/containers/get-a-container", - "api-docs/api-reference/containers/refresh-container", - "api-docs/api-reference/containers/get-container-route", - "api-docs/api-reference/containers/get-a-containers-raw-events", - "api-docs/api-reference/containers/get-a-containers-transport-events" - ] - }, - { - "group": "Shipping Lines", - "pages": [ - "api-docs/api-reference/shipping-lines/shipping-lines", - "api-docs/api-reference/shipping-lines/get-a-single-shipping-line" - ] - }, - { - "group": "Metro Areas", - "pages": [ - "api-docs/api-reference/metro-areas/get-a-metro-area-using-the-unlocode-or-the-id" - ] - }, - { - "group": "Ports", - "pages": [ - "api-docs/api-reference/ports/get-a-port-using-the-locode-or-the-id" - ] - }, - { - "group": "Vessels", - "pages": [ - "api-docs/api-reference/vessels/get-a-vessel-using-the-id", - "api-docs/api-reference/vessels/get-a-vessel-using-the-imo", - "api-docs/api-reference/vessels/get-vessel-future-positions", - "api-docs/api-reference/vessels/get-vessel-future-positions-with-coordinates" - ] - }, - { - "group": "Terminals", - "pages": ["api-docs/api-reference/terminals/get-a-terminal-using-the-id"] - }, - { - "group": "Parties", - "pages": [ - "api-docs/api-reference/parties/list-parties", - "api-docs/api-reference/parties/create-a-party", - "api-docs/api-reference/parties/get-a-party", - "api-docs/api-reference/parties/edit-a-party" - ] - } - ], - "footerSocials": { - "twitter": "https://twitter.com/terminal49", - "linkedin": "https://www.linkedin.com/company/terminal49/" - }, - "redirects": [ - { - "source": "/api-docs/getting-started/recieve-status-updates", - "destination": "/api-docs/getting-started/receive-status-updates" - } - ] -} diff --git a/docs/openapi.json b/docs/openapi.json deleted file mode 100644 index b4ffa5db..00000000 --- a/docs/openapi.json +++ /dev/null @@ -1,9619 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "Terminal49 API Reference", - "version": "0.2.0", - "contact": { - "name": "Terminal49 API support", - "url": "https://www.terminal49.com", - "email": "support@terminal49.com" - }, - "description": "The Terminal 49 API offers a convenient way to programmatically track your shipments from origin to destination.\n\nPlease enter your API key into the \"Variables\" tab before using these endpoints within Postman.", - "x-label": "Beta", - "termsOfService": "https://www.terminal49.com/terms" - }, - "servers": [ - { - "url": "https://api.terminal49.com/v2", - "description": "Production" - } - ], - "paths": { - "/shipments": { - "get": { - "summary": "List shipments", - "tags": [ - "Shipments" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/shipment" - } - }, - "included": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/container" - }, - { - "$ref": "#/components/schemas/port" - }, - { - "$ref": "#/components/schemas/terminal" - } - ] - } - }, - "links": { - "$ref": "#/components/schemas/links" - }, - "meta": { - "$ref": "#/components/schemas/meta" - } - } - }, - "examples": { - "En-route to NY with inland move": { - "value": { - "data": [ - { - "id": "62738624-7032-4a50-892e-c55826228c25", - "type": "shipment", - "attributes": { - "created_at": "2024-06-26T17:28:59Z", - "ref_numbers": [], - "tags": [], - "bill_of_lading_number": "OOLU2148468620", - "normalized_number": "2148468620", - "shipping_line_scac": "OOLU", - "shipping_line_name": "Orient Overseas Container Line", - "shipping_line_short_name": "OOCL", - "customer_name": "Sodor Steamworks", - "port_of_lading_locode": "CNNBG", - "port_of_lading_name": "Ningbo", - "port_of_discharge_locode": "USLAX", - "port_of_discharge_name": "Los Angeles", - "pod_vessel_name": "EVER FORWARD", - "pod_vessel_imo": "9850551", - "pod_voyage_number": "1119E", - "destination_locode": "USCLE", - "destination_name": "Cleveland", - "destination_timezone": "America/New_York", - "destination_ata_at": null, - "destination_eta_at": "2024-07-02T08:00:00Z", - "pol_etd_at": null, - "pol_atd_at": "2024-06-09T03:42:00Z", - "pol_timezone": "Asia/Shanghai", - "pod_eta_at": null, - "pod_original_eta_at": null, - "pod_ata_at": "2024-06-22T13:36:00Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": "2024-06-26T17:28:59Z", - "line_tracking_last_succeeded_at": "2024-06-26T17:28:59Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "links": { - "self": "/v2/shipments/62738624-7032-4a50-892e-c55826228c25" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "2a90c27b-c2ee-45e2-892f-5c695d74e2d0", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "47b27584-4ec9-4e2f-95e1-7a42928cc40c", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "42c53b13-0d29-4fa6-8663-7343a56319f1", - "type": "terminal" - } - }, - "destination": { - "data": { - "id": "3b1cc325-ffe9-400a-82ce-f5c4891af382", - "type": "port" - } - }, - "destination_terminal": { - "data": { - "id": "ce22669e-14b2-4501-b782-f0a360f07cd0", - "type": "terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "7aefc29e-0898-4825-8376-4f998b51d033", - "type": "container" - } - ] - } - } - }, - { - "id": "baaa725e-aa0e-4937-ac78-54d9e2e8621e", - "type": "shipment", - "attributes": { - "created_at": "2024-06-26T16:47:42Z", - "ref_numbers": [], - "tags": [], - "bill_of_lading_number": "HDMUTAOM72244900", - "normalized_number": "TAOM72244900", - "shipping_line_scac": "HDMU", - "shipping_line_name": "Hyundai Merchant Marine", - "shipping_line_short_name": "Hyundai", - "customer_name": "Sodor Steamworks", - "port_of_lading_locode": "CNQDG", - "port_of_lading_name": "Qingdao", - "port_of_discharge_locode": "USSAV", - "port_of_discharge_name": "Savannah", - "pod_vessel_name": "UMM SALAL", - "pod_vessel_imo": "9525857", - "pod_voyage_number": "0038E", - "destination_locode": "USROQ", - "destination_name": "Rossville", - "destination_timezone": "America/Chicago", - "destination_ata_at": null, - "destination_eta_at": "2024-06-30T07:05:00Z", - "pol_etd_at": null, - "pol_atd_at": "2024-05-07T03:01:00Z", - "pol_timezone": "Asia/Shanghai", - "pod_eta_at": null, - "pod_original_eta_at": null, - "pod_ata_at": "2024-06-20T21:27:00Z", - "pod_timezone": "America/New_York", - "line_tracking_last_attempted_at": "2024-06-26T16:48:04Z", - "line_tracking_last_succeeded_at": "2024-06-26T16:48:04Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "links": { - "self": "/v2/shipments/baaa725e-aa0e-4937-ac78-54d9e2e8621e" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "0ccbe8af-c8d0-4abd-a842-3bfad1d82024", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "6129528d-846e-4571-ae16-b5328a4285ab", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "a243bdf8-0da3-4056-a6a7-05fe8ab43999", - "type": "terminal" - } - }, - "destination": { - "data": { - "id": "87ca3f37-e4d1-46eb-9eb1-6b5ffafde95d", - "type": "port" - } - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "772cd872-9677-4c68-9b7a-4e9e843b00e2", - "type": "container" - }, - { - "id": "52efc544-0de1-452f-bcf8-0290a6ce5c11", - "type": "container" - }, - { - "id": "3107692e-61ad-4b4c-b3d4-78348b2e37ff", - "type": "container" - } - ] - } - } - }, - { - "id": "7721a48c-5e93-43c9-9f5f-5be10a87fdde", - "type": "shipment", - "attributes": { - "created_at": "2024-06-26T16:28:39Z", - "ref_numbers": [], - "tags": [], - "bill_of_lading_number": "OOLU2738424980", - "normalized_number": "2738424980", - "shipping_line_scac": "OOLU", - "shipping_line_name": "Orient Overseas Container Line", - "shipping_line_short_name": "OOCL", - "customer_name": "Sodor Steamworks", - "port_of_lading_locode": "ITSPE", - "port_of_lading_name": "La Spezia", - "port_of_discharge_locode": "USSAV", - "port_of_discharge_name": "Savannah", - "pod_vessel_name": "OOCL GUANGZHOU", - "pod_vessel_imo": "9404869", - "pod_voyage_number": "162E", - "destination_locode": "USATL", - "destination_name": "Atlanta", - "destination_timezone": "America/New_York", - "destination_ata_at": null, - "destination_eta_at": "2024-06-27T06:42:00Z", - "pol_etd_at": null, - "pol_atd_at": "2024-06-05T08:03:00Z", - "pol_timezone": "Europe/Rome", - "pod_eta_at": null, - "pod_original_eta_at": null, - "pod_ata_at": "2024-06-23T15:34:00Z", - "pod_timezone": "America/New_York", - "line_tracking_last_attempted_at": "2024-06-26T16:28:39Z", - "line_tracking_last_succeeded_at": "2024-06-26T16:28:39Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "links": { - "self": "/v2/shipments/7721a48c-5e93-43c9-9f5f-5be10a87fdde" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "b5656766-a56f-4b32-8e03-d240e7519604", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "6129528d-846e-4571-ae16-b5328a4285ab", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "a243bdf8-0da3-4056-a6a7-05fe8ab43999", - "type": "terminal" - } - }, - "destination": { - "data": { - "id": "7daf9ea3-3018-4d62-b88c-43803df9030c", - "type": "port" - } - }, - "destination_terminal": { - "data": { - "id": "022ef8fc-1e2a-4ad6-8eae-330d65eb1c8e", - "type": "terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "2a25fd3e-a18e-47cc-9cea-62771e82d0f2", - "type": "container" - }, - { - "id": "6cdc725d-2b31-40f9-86dd-76225390a488", - "type": "container" - }, - { - "id": "2f1e9a9d-4689-4f4d-84a8-64409b56521d", - "type": "container" - } - ] - } - } - }, - { - "id": "32b5ad78-43ba-42d9-bdc0-4cf12320e020", - "type": "shipment", - "attributes": { - "created_at": "2024-06-26T15:59:52Z", - "ref_numbers": [], - "tags": [], - "bill_of_lading_number": "OOLU2738277190", - "normalized_number": "2738277190", - "shipping_line_scac": "OOLU", - "shipping_line_name": "Orient Overseas Container Line", - "shipping_line_short_name": "OOCL", - "customer_name": "Sodor Steamworks", - "port_of_lading_locode": "CNQDG", - "port_of_lading_name": "Qingdao", - "port_of_discharge_locode": "USLAX", - "port_of_discharge_name": "Los Angeles", - "pod_vessel_name": "EVER FORWARD", - "pod_vessel_imo": "9850551", - "pod_voyage_number": "1119E", - "destination_locode": "USMEM", - "destination_name": "Memphis", - "destination_timezone": "America/Chicago", - "destination_ata_at": null, - "destination_eta_at": "2024-07-01T14:00:00Z", - "pol_etd_at": null, - "pol_atd_at": "2024-06-02T18:22:00Z", - "pol_timezone": "Asia/Shanghai", - "pod_eta_at": null, - "pod_original_eta_at": null, - "pod_ata_at": "2024-06-22T13:36:00Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": "2024-06-26T15:59:52Z", - "line_tracking_last_succeeded_at": "2024-06-26T15:59:52Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "links": { - "self": "/v2/shipments/32b5ad78-43ba-42d9-bdc0-4cf12320e020" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "0ccbe8af-c8d0-4abd-a842-3bfad1d82024", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "47b27584-4ec9-4e2f-95e1-7a42928cc40c", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "42c53b13-0d29-4fa6-8663-7343a56319f1", - "type": "terminal" - } - }, - "destination": { - "data": { - "id": "ba0b9f68-2025-40ca-ae12-1c2210cca333", - "type": "port" - } - }, - "destination_terminal": { - "data": { - "id": "d0ec0da1-8a3a-4b11-b1e3-5716bbc71dc3", - "type": "terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "60d2fd62-14e2-4ca2-a927-9633fc58fdff", - "type": "container" - }, - { - "id": "0ffe3e75-b3df-4d20-b9f4-97bbbcec404d", - "type": "container" - } - ] - } - } - }, - { - "id": "bd117d3b-8fa4-487c-9bab-25c15e227d1a", - "type": "shipment", - "attributes": { - "created_at": "2024-06-26T15:59:35Z", - "ref_numbers": [], - "tags": [], - "bill_of_lading_number": "OOLU2147973020", - "normalized_number": "2147973020", - "shipping_line_scac": "OOLU", - "shipping_line_name": "Orient Overseas Container Line", - "shipping_line_short_name": "OOCL", - "customer_name": "Sodor Steamworks", - "port_of_lading_locode": "IDSUB", - "port_of_lading_name": "Surabaya", - "port_of_discharge_locode": "USLAX", - "port_of_discharge_name": "Los Angeles", - "pod_vessel_name": "CMA CGM A.LINCOLN", - "pod_vessel_imo": "9780859", - "pod_voyage_number": "1TU70E1MA", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2024-05-14T02:37:00Z", - "pol_timezone": "Asia/Jakarta", - "pod_eta_at": null, - "pod_original_eta_at": null, - "pod_ata_at": "2024-06-23T21:58:00Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": "2024-06-26T15:59:35Z", - "line_tracking_last_succeeded_at": "2024-06-26T15:59:35Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "links": { - "self": "/v2/shipments/bd117d3b-8fa4-487c-9bab-25c15e227d1a" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "4201ab42-c51f-48ac-b7a1-12146e02c6a2", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "47b27584-4ec9-4e2f-95e1-7a42928cc40c", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "eaa2580c-5f5b-4198-85e4-821145d62098", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "815ff702-fdb5-4455-a28e-314c345d7481", - "type": "container" - } - ] - } - } - } - ], - "meta": { - "size": 5, - "total": 34044 - }, - "links": { - "self": "https://api.terminal49.com/v2/shipments?page[size]=5", - "current": "https://api.terminal49.com/v2/shipments?page[number]=1&page[size]=5", - "next": "https://api.terminal49.com/v2/shipments?page[number]=2&page[size]=5", - "last": "https://api.terminal49.com/v2/shipments?page[number]=6809&page[size]=5" - } - } - } - } - } - } - }, - "422": { - "description": "Unprocessable Entity", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/error" - } - } - } - }, - "examples": { - "Errors": { - "value": { - "error": [ - { - "title": "Invalid arrival_date_from", - "detail": "filter['arrival_from'] must be in the format 'YYYY-MM-DD' and a valid date" - }, - { - "title": "Invalid arrival_date_to", - "detail": "filter['arrival_from'] must be in the format 'YYYY-MM-DD' and a valid date" - }, - { - "title": "Invalid port_of_lading", - "detail": "port_of_discharge must be an array of 5 character UN/LOCODEs" - }, - { - "title": "Invalid port_of_discharge", - "detail": "port_of_discharge must be an array of 5 character UN/LOCODEs" - } - ] - } - } - } - } - } - } - }, - "operationId": "get-shipments", - "description": "Returns a list of your shipments. The shipments are returned sorted by creation date, with the most recent shipments appearing first.\n\nThis api will return all shipments associated with the account. Shipments created via the `tracking_request` API aswell as the ones added via the dashboard will be retuned via this endpoint. ", - "parameters": [ - { - "schema": { - "type": "integer", - "default": 1 - }, - "in": "query", - "name": "page[number]", - "description": "\n" - }, - { - "schema": { - "type": "integer", - "default": 30 - }, - "in": "query", - "name": "page[size]", - "description": "\n" - }, - { - "schema": { - "type": "string" - }, - "in": "query", - "name": "q", - "description": "\nSearch shipments by master bill of lading, reference number, or container number.", - "deprecated": true - }, - { - "schema": { - "type": "string" - }, - "in": "query", - "name": "include", - "description": "Comma delimited list of relations to include" - }, - { - "schema": { - "type": "string" - }, - "in": "query", - "name": "number", - "description": "Search shipments by the original request tracking `request_number`" - }, - { - "schema": { - "type": "boolean" - }, - "in": "query", - "name": "filter[tracking_stopped]", - "description": "Filter shipments by whether they are still tracking or not" - } - ] - } - }, - "/shipments/{id}": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true, - "description": "Shipment Id" - } - ], - "get": { - "summary": "Get a shipment", - "tags": [ - "Shipments" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/shipment" - }, - "included": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/container" - }, - { - "$ref": "#/components/schemas/port" - }, - { - "$ref": "#/components/schemas/terminal" - } - ] - } - } - } - }, - "examples": { - "En-route to NY with inland move": { - "value": { - "data": { - "id": "02b1bd6f-407c-45bb-8645-06e7ee34e7e3", - "type": "shipment", - "attributes": { - "created_at": "2024-06-26T15:05:20Z", - "ref_numbers": [], - "tags": [], - "bill_of_lading_number": "MEDUF5399896", - "normalized_number": "MEDUF5399896", - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "shipping_line_short_name": "MSC", - "customer_name": "Sodor Steamworks", - "port_of_lading_locode": "FRLEH", - "port_of_lading_name": "Le Havre", - "port_of_discharge_locode": "USNYC", - "port_of_discharge_name": "New York / New Jersey", - "pod_vessel_name": "MSC ANISHA R.", - "pod_vessel_imo": "9227297", - "pod_voyage_number": "421A", - "destination_locode": "USIND", - "destination_name": "Indianapolis", - "destination_timezone": "US/Eastern", - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2024-06-11T22:00:00Z", - "pol_timezone": "Europe/Paris", - "pod_eta_at": null, - "pod_original_eta_at": null, - "pod_ata_at": null, - "pod_timezone": "America/New_York", - "line_tracking_last_attempted_at": "2024-06-26T15:05:20Z", - "line_tracking_last_succeeded_at": "2024-06-26T15:05:20Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "links": { - "self": "/v2/shipments/02b1bd6f-407c-45bb-8645-06e7ee34e7e3" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "fe7fc831-8a81-4079-8938-b90015912e8b", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "b859f5c3-8515-41da-bf20-39c0a5ada887", - "type": "terminal" - } - }, - "destination": { - "data": { - "id": "e1e492f6-c8ba-45a9-ad1a-67a7e74547ce", - "type": "port" - } - }, - "destination_terminal": { - "data": null - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "55a700e4-7005-45a9-92fd-1ff38641dbd9", - "type": "container" - } - ] - } - } - }, - "links": { - "self": "https://api.terminal49.com/v2/shipments/02b1bd6f-407c-45bb-8645-06e7ee34e7e3?include=containers" - }, - "included": [ - { - "id": "55a700e4-7005-45a9-92fd-1ff38641dbd9", - "type": "container", - "attributes": { - "number": "CAIU7432986", - "seal_number": null, - "created_at": "2024-06-26T15:05:21Z", - "ref_numbers": [], - "pod_arrived_at": null, - "pod_discharged_at": "2024-06-22T04:00:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [], - "available_for_pickup": true, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": null, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": "2024-06-26T17:51:12Z", - "fees_at_pod_terminal": [], - "pickup_lfd": "2024-07-07T04:00:00Z", - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "Yard - Y0709A", - "pod_last_tracking_request_at": "2024-06-26T17:51:12Z", - "shipment_last_tracking_request_at": "2024-06-26T15:05:20Z", - "availability_known": true, - "pod_timezone": "America/New_York", - "final_destination_timezone": "US/Eastern", - "empty_terminated_timezone": "US/Eastern", - "pod_rail_carrier_scac": "CSXT", - "ind_rail_carrier_scac": "CSXT", - "pod_rail_loaded_at": null, - "pod_rail_departed_at": null, - "ind_eta_at": null, - "ind_ata_at": null, - "ind_rail_unloaded_at": null, - "ind_facility_lfd_on": null - }, - "relationships": { - "shipment": { - "data": { - "id": "02b1bd6f-407c-45bb-8645-06e7ee34e7e3", - "type": "shipment" - } - }, - "pickup_facility": { - "data": null - }, - "pod_terminal": { - "data": { - "id": "b859f5c3-8515-41da-bf20-39c0a5ada887", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "45b542cb-332b-4684-b915-42e3a0759823", - "type": "transport_event" - }, - { - "id": "174ed528-a1a9-4002-aef0-f2c9369199da", - "type": "transport_event" - }, - { - "id": "7a2f30a6-a756-4c14-9477-fbfc1c7fe2f8", - "type": "transport_event" - }, - { - "id": "e7365004-175a-46e8-96cd-dbed0f3daf21", - "type": "transport_event" - }, - { - "id": "7c567bf3-7f01-4a3d-a176-eaa1f7165585", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "2956f71c-bfb9-4e49-b9e2-1b4d53c74cac", - "type": "raw_event" - }, - { - "id": "391e0eda-65b5-4fc3-a53d-25ecd9570259", - "type": "raw_event" - }, - { - "id": "74810c04-6c8a-4194-8cff-52936584a965", - "type": "raw_event" - }, - { - "id": "4b1500e2-b23b-4896-87bd-c38b1d16f385", - "type": "raw_event" - }, - { - "id": "8b9a7d88-720a-4304-8c1e-a3336e39f481", - "type": "raw_event" - }, - { - "id": "bf1f59c5-5dd8-4013-87f9-d7056bc87114", - "type": "raw_event" - } - ] - } - } - } - ] - } - } - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/error" - } - } - } - }, - "examples": { - "En-route to NY with inland move": { - "value": { - "data": { - "id": "512ae32c-a604-44f8-9c15-2ca9cd3d2ae8", - "type": "shipment", - "attributes": { - "bill_of_lading_number": "6140575020", - "ref_numbers": [ - "my-ref-49", - "PO#18412" - ], - "created_at": "2020-02-20T13:37:12Z", - "tags": [], - "port_of_lading_locode": "INVTZ", - "port_of_lading_name": "Visakhapatnam, IN", - "port_of_discharge_locode": "USNYC", - "port_of_discharge_name": "New York / New Jersey, NY", - "destination_locode": "USDET", - "destination_name": "Detroit, MI", - "shipping_line_scac": "HLCU", - "pod_vessel_name": "CMA CGM ALMAVIVA", - "pod_voyage_number": "0108", - "pol_etd_at": null, - "pol_atd_at": "2020-02-15T21:53Z", - "pol_timezone": "Asia/Calcutta", - "pod_eta_at": "2020-03-18T08:00Z", - "pod_ata_at": null, - "pod_timezone": "America/New_York", - "destination_eta_at": "2020-03-26T17:00Z", - "destination_ata_at": null, - "destination_timezone": "America/Detroit" - }, - "relationships": { - "containers": { - "data": [ - { - "id": "c99a81c0-ff69-4bdf-aa5f-8e33a787f5fa", - "type": "container" - } - ] - }, - "port_of_lading": { - "data": { - "id": "bde5465a-1160-4fde-a026-74df9c362f65", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", - "type": "port" - } - }, - "destination": { - "data": { - "id": "c9ae2b6b-5088-4e07-ba09-2872121e4fa2", - "type": "metro_area" - } - }, - "customer": { - "data": { - "id": "ff76b51a-371e-45ec-86d1-9d03ccae566a", - "type": "account" - } - } - } - }, - "included": [ - { - "id": "c99a81c0-ff69-4bdf-aa5f-8e33a787f5fa", - "type": "container", - "attributes": { - "number": "UACU4743531", - "equipment_type": "reefer", - "length": 40, - "height": "high_cube", - "weight_in_lbs": 35075, - "created_at": "2020-02-20T08:19:52Z", - "seal_number": null, - "pickup_lfd": null, - "availability_known": false, - "available_for_pickup": null, - "current_transportation_mode": "vessel", - "pod_discharged_at": null, - "pod_picked_up_at": null, - "destination_unloaded_at": null, - "destination_picked_up_at": null, - "empty_returned_at": null, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": null, - "empty_terminated_timezone": null - }, - "relationships": { - "most_recent_location": { - "data": { - "id": "dd179094-a1d4-4129-842d-b952e43df4b7", - "type": "port" - } - }, - "shipment": { - "data": { - "id": "512ae32c-a604-44f8-9c15-2ca9cd3d2ae8", - "type": "shipment" - } - } - } - }, - { - "id": "bde5465a-1160-4fde-a026-74df9c362f65", - "type": "port", - "attributes": { - "name": "Visakhapatnam", - "code": "INVTZ", - "country_code": "IN", - "timezone": "Asia/Calcutta" - } - }, - { - "id": "dd179094-a1d4-4129-842d-b952e43df4b7", - "type": "port", - "attributes": { - "name": "Damietta", - "code": "EGDAM", - "country_code": "EG", - "time_zone": "Africa/Cairo" - } - }, - { - "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", - "type": "port", - "attributes": { - "name": "New York / New Jersey", - "code": "USNYC", - "country_code": "US", - "timezone": "America/New_York" - } - }, - { - "id": "c9ae2b6b-5088-4e07-ba09-2872121e4fa2", - "type": "metro_area", - "attributes": { - "name": "Detroit", - "code": "USDET", - "country_code": "US", - "state_abbr": "MI", - "timezone": "America/Detroit" - } - }, - { - "id": "ff76b51a-371e-45ec-86d1-9d03ccae566a", - "type": "account", - "attributes": { - "name": "A-Z Imports" - } - }, - { - "id": "252a5450-2893-4207-b5c4-81ce3152ce84", - "type": "vessel", - "attributes": { - "name": "CMA CGM ALMAVIVA", - "imo": "9450648", - "mmsi": "228339600", - "latitude": 70.22625823437389, - "longitude": 45.06279126658865, - "nautical_speed_knots": 100, - "navigational_heading_degrees": 1, - "position_timestamp": "2023-06-05T19:46:18Z" - } - } - ] - } - } - } - } - } - } - }, - "operationId": "get-shipment-id", - "description": "Retrieves the details of an existing shipment. You need only supply the unique shipment `id` that was returned upon `tracking_request` creation.", - "parameters": [ - { - "schema": { - "type": "string" - }, - "in": "query", - "name": "include", - "description": "Comma delimited list of relations to include" - } - ] - }, - "patch": { - "summary": "Edit a shipment", - "operationId": "patch-shipments-id", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/shipment" - } - } - } - } - } - } - }, - "description": "Update a shipment", - "tags": [ - "Shipments" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "object", - "required": [ - "attributes" - ], - "properties": { - "attributes": { - "type": "object", - "properties": { - "ref_numbers": { - "type": "array", - "example": [ - "REFNUMBER10" - ], - "description": "Shipment ref numbers.", - "items": { - "type": "string" - } - }, - "shipment_tags": { - "type": "array", - "x-stoplight": { - "id": "02itol38fmg55" - }, - "uniqueItems": true, - "description": "Tags related to a shipment", - "items": { - "x-stoplight": { - "id": "64x90wvr9d6nd" - }, - "type": "string", - "example": "tag1, tag2" - } - } - } - } - } - } - } - } - } - } - } - } - }, - "/shipments/{id}/stop_tracking": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "patch": { - "summary": "Stop tracking a shipment", - "operationId": "patch-shipments-id-stop-tracking", - "tags": [ - "Shipments" - ], - "description": "We'll stop tracking the shipment, which means that there will be no more updates. You can still access the shipment's previously-collected information via the API or dashboard.\n\nYou can resume tracking a shipment by calling the `resume_tracking` endpoint, but keep in mind that some information is only made available by our data sources at specific times, so a stopped and resumed shipment may have some information missing.", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/shipment" - } - } - } - } - } - } - } - } - }, - "/shipments/{id}/resume_tracking": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "patch": { - "summary": "Resume tracking a shipment", - "operationId": "patch-shipments-id-resume-tracking", - "tags": [ - "Shipments" - ], - "description": "Resume tracking a shipment. Keep in mind that some information is only made available by our data sources at specific times, so a stopped and resumed shipment may have some information missing.", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/shipment" - } - } - } - } - } - } - } - } - }, - "/tracking_requests": { - "post": { - "summary": "Create a tracking request", - "operationId": "post-track", - "responses": { - "201": { - "description": "Tracking Request Created", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/tracking_request" - }, - "included": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/account" - }, - { - "$ref": "#/components/schemas/shipping_line" - } - ] - } - } - } - }, - "examples": { - "Pending Tracking Request": { - "value": { - "data": { - "id": "ba4cb904-827f-4038-8e31-1e92b3356218", - "type": "tracking_request", - "attributes": { - "request_number": "MEDUFR030802", - "request_type": "bill_of_lading", - "scac": "MSCU", - "ref_numbers": [], - "created_at": "2020-04-04T16:13:35-07:00", - "updated_at": "2020-04-04T17:13:35-07:00", - "status": "pending", - "failed_reason": null - }, - "relationships": { - "tracked_object": { - "data": null - }, - "customer": { - "data": { - "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", - "type": "party" - } - } - }, - "links": { - "self": "/v2/tracking_requests/ba4cb904-827f-4038-8e31-1e92b3356218" - } - } - } - } - } - } - }, - "headers": {} - }, - "422": { - "description": "Unprocessable Entity", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/error" - } - } - } - }, - "examples": { - "Error Examples": { - "value": { - "errors": [ - { - "status": "422", - "source": { - "pointer": "/data/attributes/scac" - }, - "title": "Unprocessable Entity", - "detail": "Scac can't be blank", - "code": "blank" - }, - { - "status": "422", - "source": { - "pointer": "/data/attributes/scac" - }, - "title": "Unprocessable Entity", - "detail": "Scac 'XXXX' is not recognized", - "code": "blank" - }, - { - "status": "422", - "source": { - "pointer": "/data/attributes/scac" - }, - "title": "Unprocessable Entity", - "detail": "Scac 'UALC' is not supported. We do not currently integrate with Universal Africa Lines", - "code": "blank" - }, - { - "status": "422", - "source": { - "pointer": "/data/attributes/request_number" - }, - "title": "Unprocessable Entity", - "detail": "Request number can't be blank", - "code": "blank" - } - ] - } - } - } - } - } - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "attributes": { - "type": "object", - "properties": { - "request_type": { - "type": "string", - "example": "bill_of_lading", - "enum": [ - "bill_of_lading", - "booking_number", - "container" - ], - "description": " The type of document number to be supplied. Container number support is currently in BETA." - }, - "request_number": { - "type": "string", - "example": "MEDUFR030802" - }, - "scac": { - "type": "string", - "example": "MSCU", - "minLength": 4, - "maxLength": 4 - }, - "ref_numbers": { - "type": "array", - "description": "Optional list of reference numbers to be added to the shipment when tracking request completes", - "items": { - "type": "string" - } - }, - "shipment_tags": { - "type": "array", - "description": "Optional list of tags to be added to the shipment when tracking request completes", - "items": { - "type": "string" - } - } - }, - "required": [ - "request_type", - "request_number", - "scac" - ] - }, - "relationships": { - "type": "object", - "properties": { - "customer": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "party" - ] - } - } - } - } - } - } - }, - "type": { - "enum": [ - "tracking_request" - ], - "type": "string" - } - }, - "required": [ - "type" - ] - } - } - }, - "examples": { - "Example: MSC BL": { - "value": { - "data": { - "attributes": { - "request_type": "bill_of_lading", - "request_number": "MEDUFR030802", - "ref_numbers": [ - "PO12345", - "HBL12345", - "CUSREF1234" - ], - "shipment_tags": [ - "camembert" - ], - "scac": "MSCU" - }, - "relationships": { - "customer": { - "data": { - "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", - "type": "party" - } - } - }, - "type": "tracking_request" - } - } - } - } - } - }, - "description": "Create a shipment tracking request" - }, - "security": [ - { - "authorization": [] - } - ], - "description": "To track an ocean shipment, you create a new tracking request. \nTwo attributes are required to track a shipment. A `bill of lading/booking number` and a shipping line `SCAC`. \n\nOnce a tracking request is created we will attempt to fetch the shipment details and it's related containers from the shipping line. If the attempt is successful we will create in new shipment object including any related container objects. We will send a `tracking_request.succeeded` webhook notification to your webhooks. \n\nIf the attempt to fetch fails then we will send a `tracking_request.failed` webhook notification to your `webhooks`. \n\nA `tracking_request.succeeded` or `tracking_request.failed` webhook notificaiton will only be sent if you have atleast one active webhook. ", - "tags": [ - "Tracking Requests" - ], - "parameters": [] - }, - "parameters": [], - "get": { - "summary": "List tracking requests", - "operationId": "get-tracking-requests", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/tracking_request" - } - }, - "links": { - "$ref": "#/components/schemas/links" - }, - "meta": { - "$ref": "#/components/schemas/meta" - }, - "included": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/account" - }, - { - "$ref": "#/components/schemas/shipping_line" - }, - { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "shipment" - ] - }, - "links": { - "type": "object", - "properties": { - "self": { - "type": "string", - "format": "uri" - } - } - } - }, - "description": "" - } - ] - } - } - } - }, - "examples": {} - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/error" - } - } - } - } - } - } - } - }, - "description": "Returns a list of your tracking requests. The tracking requests are returned sorted by creation date, with the most recent tracking request appearing first.", - "tags": [ - "Tracking Requests" - ], - "parameters": [ - { - "schema": { - "type": "string" - }, - "in": "query", - "name": "q", - "description": "A search term to be applied against request_number and reference_numbers.", - "deprecated": true - }, - { - "schema": { - "type": "string", - "enum": [ - "created", - "pending", - "failed" - ], - "example": "created" - }, - "in": "query", - "name": "filter[status]", - "description": "filter by `status`" - }, - { - "schema": { - "type": "string", - "example": "MSCU" - }, - "in": "query", - "name": "filter[scac]", - "description": "filter by shipping line `scac`" - }, - { - "schema": { - "type": "string", - "example": "2020-04-28T22:59:15Z", - "format": "date-time" - }, - "in": "query", - "name": "filter[created_at][start]", - "description": "filter by tracking_requests `created_at` after a certain ISO8601 timestamp" - }, - { - "schema": { - "type": "string", - "format": "date-time", - "example": "2020-04-28T22:59:15Z" - }, - "in": "query", - "description": "filter by tracking_requests `created_at` before a certain ISO8601 timestamp", - "name": "filter[created_at][end]" - }, - { - "schema": { - "type": "string" - }, - "in": "query", - "name": "include", - "description": "Comma delimited list of relations to include. 'tracked_object' is included by default." - }, - { - "schema": { - "type": "integer" - }, - "in": "query", - "name": "page[number]" - }, - { - "schema": { - "type": "integer" - }, - "in": "query", - "name": "page[size]" - }, - { - "schema": { - "type": "string" - }, - "in": "query", - "name": "filter[request_number]", - "description": "filter by `request_number`" - } - ] - } - }, - "/tracking_requests/{id}": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true, - "description": "Tracking Request ID" - } - ], - "get": { - "summary": "Get a single tracking request", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/tracking_request" - }, - "included": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/account" - }, - { - "$ref": "#/components/schemas/shipment" - }, - { - "$ref": "#/components/schemas/shipping_line" - } - ] - } - } - } - }, - "examples": { - "With Status Created": { - "value": { - "data": { - "id": "ba4cb904-827f-4038-8e31-1e92b3356218", - "type": "tracking_request", - "attributes": { - "request_number": "MEDUFR030802", - "request_type": "bill_of_lading", - "scac": "MSCU", - "ref_numbers": [], - "created_at": "2020-04-04T16:13:35-07:00", - "updated_at": "2020-04-04T17:13:35-07:00", - "status": "created", - "failed_reason": null - }, - "relationships": { - "tracked_object": { - "data": { - "id": "eb6f218a-0b4a-47f9-8ef9-759aa5e0ea83", - "type": "shipment" - } - } - }, - "links": { - "self": "/v2/tracking_requests/ba4cb904-827f-4038-8e31-1e92b3356218" - } - }, - "included": [ - { - "id": "eb6f218a-0b4a-47f9-8ef9-759aa5e0ea83", - "type": "shipment", - "attributes": { - "created_at": "2020-04-04T16:13:37-07:00", - "bill_of_lading_number": "MEDUFR030802", - "ref_numbers": [], - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "port_of_lading_locode": "FRFOS", - "port_of_lading_name": "Fos-Sur-Mer", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Oakland", - "pod_vessel_name": "MSC ALGECIRAS", - "pod_vessel_imo": "9605243", - "pod_voyage_number": "920A", - "destination_locode": "USOAK", - "destination_name": "Oakland", - "destination_timezone": "America/Los_Angeles", - "destination_ata_at": "2019-06-21T18:46:00-07:00", - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2019-05-24T03:00:00-07:00", - "pol_timezone": "Europe/Paris", - "pod_eta_at": null, - "pod_ata_at": "2019-06-21T18:46:00-07:00", - "pod_timezone": "America/Los_Angeles" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "6d8c6c29-72a6-49ad-87b7-fd045f202212", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port" - } - }, - "pod_terminal": { - "data": null - }, - "destination": { - "data": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port" - } - }, - "containers": { - "data": [ - { - "id": "11c1fa10-52a5-48e2-82f4-5523756b3d0f", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/eb6f218a-0b4a-47f9-8ef9-759aa5e0ea83" - } - } - ] - } - }, - "Multiple containers": { - "value": { - "data": { - "id": "62c30bd4-d7fc-40dc-9fd6-fb39224301f5", - "type": "tracking_request", - "attributes": { - "request_number": "212157148", - "request_type": "bill_of_lading", - "scac": "MAEU", - "ref_numbers": [], - "shipment_tags": [], - "created_at": "2021-07-27T16:44:14Z", - "updated_at": "2021-07-27T17:44:14Z", - "status": "created", - "failed_reason": null, - "is_retrying": false, - "retry_count": null - }, - "relationships": { - "tracked_object": { - "data": { - "id": "dfc9f601-f6fe-412e-a71c-feabcc2dd4e3", - "type": "shipment" - } - }, - "customer": { - "data": null - } - }, - "links": { - "self": "/v2/tracking_requests/62c30bd4-d7fc-40dc-9fd6-fb39224301f5" - } - }, - "links": { - "self": "https://api.terminal49.com/v2/tracking_requests/62c30bd4-d7fc-40dc-9fd6-fb39224301f5?filter%5Bstatus%5D=created" - }, - "included": [ - { - "id": "dfc9f601-f6fe-412e-a71c-feabcc2dd4e3", - "type": "shipment", - "attributes": { - "created_at": "2021-07-27T16:44:16Z", - "ref_numbers": null, - "tags": [], - "bill_of_lading_number": "212157148", - "shipping_line_scac": "MAEU", - "shipping_line_name": "Maersk", - "shipping_line_short_name": "Maersk", - "port_of_lading_locode": "MYTPP", - "port_of_lading_name": "Tanjung Pelepas", - "port_of_discharge_locode": null, - "port_of_discharge_name": null, - "pod_vessel_name": null, - "pod_vessel_imo": null, - "pod_voyage_number": null, - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": null, - "pol_timezone": "Asia/Kuala_Lumpur", - "pod_eta_at": "2021-09-15T15:00:00Z", - "pod_ata_at": null, - "pod_timezone": null, - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": "2021-07-27T16:44:16Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "6c387786-252c-476d-9f99-7d835b6b978b", - "type": "port" - } - }, - "port_of_discharge": { - "data": null - }, - "pod_terminal": { - "data": null - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": null - }, - "containers": { - "data": [ - { - "id": "965880c9-a37e-4ed7-a060-9c49c0f0c5ed", - "type": "container" - }, - { - "id": "ea1f8e08-fcdf-498d-9cb5-0c370b023eeb", - "type": "container" - }, - { - "id": "67f55105-8ea2-4137-9244-f9cc204f5766", - "type": "container" - }, - { - "id": "5ab5d058-772c-466c-bc73-0b8767ad5a79", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/dfc9f601-f6fe-412e-a71c-feabcc2dd4e3" - } - } - ] - } - } - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/error" - } - } - } - }, - "examples": {} - } - } - } - }, - "operationId": "get-track-request-by-id", - "description": "Get the details and status of an existing tracking request. ", - "tags": [ - "Tracking Requests" - ], - "parameters": [ - { - "schema": { - "type": "string" - }, - "in": "query", - "name": "include", - "description": "Comma delimited list of relations to include. 'tracked_object' is included by default." - } - ], - "x-internal": false - }, - "patch": { - "summary": "Edit a tracking request", - "operationId": "patch-track-request-by-id", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/tracking_request" - } - } - } - } - } - } - }, - "description": "Update a tracking request", - "tags": [ - "Tracking Requests" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "object", - "required": [ - "attributes", - "type" - ], - "properties": { - "attributes": { - "type": "object", - "properties": { - "ref_number": { - "type": "string", - "example": "REFNUMBER11", - "description": "Tracking request ref number." - } - } - } - } - } - } - } - } - } - } - } - }, - "/webhooks/{id}": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "get": { - "summary": "Get single webhook", - "tags": [ - "Webhooks" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/webhook" - } - } - } - } - } - } - }, - "operationId": "get-webhooks-id", - "description": "Get the details of a single webhook" - }, - "patch": { - "summary": "Edit a webhook", - "operationId": "patch-webhooks-id", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/webhook" - } - } - } - } - } - } - }, - "description": "Update a single webhook", - "tags": [ - "Webhooks" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "object", - "required": [ - "attributes", - "type" - ], - "properties": { - "attributes": { - "type": "object", - "properties": { - "url": { - "type": "string", - "example": "https://webhook.site/#!/39084fbb-d887-42e8-be08-b9183ad02362", - "format": "uri", - "description": "The URL of the webhook endpoint." - }, - "events": { - "type": "array", - "description": "The list of events to enable for this endpoint.", - "uniqueItems": true, - "minItems": 1, - "items": { - "type": "string", - "enum": [ - "container.transport.vessel_arrived", - "container.transport.vessel_discharged", - "container.transport.vessel_loaded", - "container.transport.vessel_departed", - "container.transport.rail_departed", - "container.transport.rail_arrived", - "container.transport.rail_loaded", - "container.transport.rail_unloaded", - "container.transport.transshipment_arrived", - "container.transport.transshipment_discharged", - "container.transport.transshipment_loaded", - "container.transport.transshipment_departed", - "container.transport.feeder_arrived", - "container.transport.feeder_discharged", - "container.transport.feeder_loaded", - "container.transport.feeder_departed", - "container.transport.empty_out", - "container.transport.full_in", - "container.transport.full_out", - "container.transport.empty_in", - "container.transport.vessel_berthed", - "shipment.estimated.arrival", - "tracking_request.succeeded", - "tracking_request.failed", - "tracking_request.awaiting_manifest", - "tracking_request.tracking_stopped", - "container.created", - "container.updated", - "container.pod_terminal_changed", - "container.transport.arrived_at_inland_destination", - "container.transport.estimated.arrived_at_inland_destination", - "container.pickup_lfd.changed", - "container.transport.available" - ] - } - }, - "active": { - "type": "boolean" - }, - "headers": { - "type": "array", - "description": "Optional custom headers to pass with each webhook invocation", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the header. (Please not this will be auto-capitalized) " - }, - "value": { - "type": "string", - "description": "The value to pass for the header\n" - } - } - } - } - } - }, - "type": { - "type": "string", - "enum": [ - "webhook" - ] - } - } - } - }, - "required": [ - "data" - ] - }, - "examples": {} - } - } - } - }, - "delete": { - "summary": "Delete a webhook", - "operationId": "delete-webhooks-id", - "responses": { - "200": { - "description": "OK" - } - }, - "description": "Delete a webhook", - "tags": [ - "Webhooks" - ] - } - }, - "/webhooks": { - "get": { - "summary": "List webhooks", - "tags": [ - "Webhooks" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/webhook" - } - }, - "meta": { - "$ref": "#/components/schemas/meta" - }, - "links": { - "$ref": "#/components/schemas/links" - } - } - }, - "examples": { - "example-1": { - "value": { - "data": [ - { - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "type": "webhook", - "attributes": { - "url": "http://example.com", - "active": true, - "events": [ - "tracking_request.succeeded" - ], - "secret": "672bd7b58b54645934a830d8fa", - "headers": [ - { - "name": "x-secret-sauce", - "value": "sriracha" - } - ] - } - } - ], - "meta": { - "size": 0, - "total": 0 - }, - "links": { - "last": "http://example.com", - "next": "http://example.com", - "prev": "http://example.com", - "first": "http://example.com", - "self": "http://example.com" - } - } - } - } - } - } - } - }, - "operationId": "get-webhooks", - "description": "Get a list of all the webhooks", - "parameters": [ - { - "schema": { - "type": "integer" - }, - "in": "query", - "name": "page[number]" - }, - { - "schema": { - "type": "integer" - }, - "in": "query", - "name": "page[size]" - } - ] - }, - "post": { - "summary": "Create a webhook", - "operationId": "post-webhooks", - "tags": [ - "Webhooks" - ], - "description": "You can configure a webhook via the API to be notified about events that happen in your Terminal49 account. These events can be realted to tracking_requests, shipments and containers. \n\nThis is the recommended way tracking shipments and containers via the API. You should use this instead of polling our the API periodically. ", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "object", - "required": [ - "attributes", - "type" - ], - "properties": { - "attributes": { - "type": "object", - "required": [ - "url", - "active" - ], - "properties": { - "url": { - "type": "string", - "example": "https://webhook.site/#!/39084fbb-d887-42e8-be08-b9183ad02362", - "format": "uri", - "description": "The URL of the webhook endpoint." - }, - "events": { - "type": "array", - "uniqueItems": true, - "description": "The list of events to enable for this endpoint.", - "minItems": 1, - "items": { - "type": "string", - "enum": [ - "container.transport.vessel_arrived", - "container.transport.vessel_discharged", - "container.transport.vessel_loaded", - "container.transport.vessel_departed", - "container.transport.rail_departed", - "container.transport.rail_arrived", - "container.transport.rail_loaded", - "container.transport.rail_unloaded", - "container.transport.transshipment_arrived", - "container.transport.transshipment_discharged", - "container.transport.transshipment_loaded", - "container.transport.transshipment_departed", - "container.transport.feeder_arrived", - "container.transport.feeder_discharged", - "container.transport.feeder_loaded", - "container.transport.feeder_departed", - "container.transport.empty_out", - "container.transport.full_in", - "container.transport.full_out", - "container.transport.empty_in", - "container.transport.vessel_berthed", - "shipment.estimated.arrival", - "tracking_request.succeeded", - "tracking_request.failed", - "tracking_request.awaiting_manifest", - "tracking_request.tracking_stopped", - "container.created", - "container.updated", - "container.pod_terminal_changed", - "container.transport.arrived_at_inland_destination", - "container.transport.estimated.arrived_at_inland_destination", - "container.pickup_lfd.changed", - "container.transport.available" - ], - "example": "tracking_request.succeeded" - } - }, - "active": { - "type": "boolean" - }, - "headers": { - "type": "array", - "description": "Optional custom headers to pass with each webhook invocation", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the header. (Please note this will be auto-capitalized) " - }, - "value": { - "type": "string", - "description": "The value to pass for the header\n" - } - } - } - } - } - }, - "type": { - "type": "string", - "enum": [ - "webhook" - ] - } - } - } - }, - "required": [ - "data" - ] - }, - "examples": { - "Test Webhook (all events)": { - "value": { - "data": { - "attributes": { - "url": "https://webhook.site/", - "events": [ - "container.transport.vessel_arrived", - "container.transport.vessel_discharged", - "container.transport.vessel_loaded", - "container.transport.vessel_departed", - "container.transport.rail_departed", - "container.transport.rail_arrived", - "container.transport.rail_loaded", - "container.transport.rail_unloaded", - "container.transport.transshipment_arrived", - "container.transport.transshipment_discharged", - "container.transport.transshipment_loaded", - "container.transport.transshipment_departed", - "container.transport.feeder_arrived", - "container.transport.feeder_discharged", - "container.transport.feeder_loaded", - "container.transport.feeder_departed", - "container.transport.empty_out", - "container.transport.full_in", - "container.transport.full_out", - "container.transport.empty_in", - "container.transport.vessel_berthed", - "shipment.estimated.arrival", - "tracking_request.succeeded", - "tracking_request.failed", - "tracking_request.awaiting_manifest", - "tracking_request.tracking_stopped", - "container.created", - "container.updated", - "container.pod_terminal_changed", - "container.transport.arrived_at_inland_destination", - "container.transport.estimated.arrived_at_inland_destination", - "container.pickup_lfd.changed", - "container.transport.available" - ], - "active": true - }, - "type": "webhook" - } - } - } - } - } - }, - "description": "" - }, - "responses": { - "201": { - "description": "Create a test webhook endpoint", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/webhook" - } - } - }, - "examples": { - "Test Endpoint Created": { - "value": { - "data": { - "type": "webhook", - "id": "9809fb96-7754-488f-99df-29ca8d410d89", - "attributes": { - "url": "https://webhook.site/", - "active": true, - "events": [ - "tracking_request.succeeded" - ], - "secret": "C193J3QOXMFH", - "created_at": "2020-06-05T19:06:13Z" - } - } - } - } - } - }, - "application/xml": { - "schema": { - "$ref": "#/components/schemas/webhook" - } - } - } - } - } - }, - "parameters": [] - }, - "/webhook_notifications/{id}": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "get": { - "summary": "Get a single webhook notification", - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/webhook_notification" - }, - "included": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/webhook" - }, - { - "$ref": "#/components/schemas/tracking_request" - }, - { - "$ref": "#/components/schemas/transport_event" - }, - { - "$ref": "#/components/schemas/estimated_event" - }, - { - "$ref": "#/components/schemas/container_updated_event" - } - ] - } - } - } - }, - "examples": { - "Tracking Request": { - "value": { - "data": { - "id": "a76187fc-5749-43f9-9053-cfaad9790a31", - "type": "webhook_notification", - "attributes": { - "id": "a76187fc-5749-43f9-9053-cfaad9790a31", - "event": "tracking_request.succeeded", - "delivery_status": "pending", - "created_at": "2020-09-11T21:25:34Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "bdeca506-9741-4ab1-a0a7-cfd1d908e923", - "type": "tracking_request" - } - }, - "webhook": { - "data": { - "id": "914b21ce-dd7d-4c49-8503-65aba488e9a9", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [] - } - } - }, - "included": [ - { - "id": "bdeca506-9741-4ab1-a0a7-cfd1d908e923", - "type": "tracking_request", - "attributes": { - "request_number": "TE497ED1063E", - "request_type": "bill_of_lading", - "scac": "MSCU", - "ref_numbers": [], - "created_at": "2020-09-11T21:25:34Z", - "updated_at": "2020-09-11T22:25:34Z", - "status": "created", - "failed_reason": null, - "is_retrying": false, - "retry_count": null - }, - "relationships": { - "tracked_object": { - "data": { - "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", - "type": "shipment" - } - } - }, - "links": { - "self": "/v2/tracking_requests/bdeca506-9741-4ab1-a0a7-cfd1d908e923" - } - }, - { - "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", - "type": "shipment", - "attributes": { - "created_at": "2020-09-11T21:25:33Z", - "bill_of_lading_number": "TE497ED1063E", - "ref_numbers": [], - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2020-08-29T21:25:33Z", - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2020-09-18T21:25:33Z", - "pod_ata_at": null, - "pod_timezone": "America/Los_Angeles" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "4384d6a5-5ccc-43b7-8d19-4a9525e74c08", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "2a765fdd-c479-4345-b71d-c4ef839952e2", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "17891bc8-52da-40bf-8ff0-0247ec05faf1", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "containers": { - "data": [ - { - "id": "b2fc728c-e2f5-4a99-8899-eb7b34ef22d7", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/b5b10c0a-8d18-46da-b4c2-4e5fa790e7da" - } - }, - { - "id": "b2fc728c-e2f5-4a99-8899-eb7b34ef22d7", - "type": "container", - "attributes": { - "number": "ARDU1824900", - "seal_number": "139F1451", - "created_at": "2020-09-11T21:25:34Z", - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": 53507, - "fees_at_pod_terminal": [], - "holds_at_pod_terminal": [], - "pickup_lfd": null, - "pickup_appointment_at": null, - "availability_known": true, - "available_for_pickup": false, - "pod_arrived_at": null, - "pod_discharged_at": null, - "final_destination_full_out_at": null, - "pod_full_out_at": null, - "empty_terminated_at": null, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": null, - "empty_terminated_timezone": null - }, - "relationships": { - "shipment": { - "data": { - "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "17891bc8-52da-40bf-8ff0-0247ec05faf1", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "56078596-5293-4c84-9245-cca00a787265", - "type": "transport_event" - } - ] - } - } - }, - { - "id": "56078596-5293-4c84-9245-cca00a787265", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_departed", - "created_at": "2020-09-11T21:25:34Z", - "voyage_number": null, - "timestamp": "2020-08-29T21:25:33Z", - "location_locode": "MXZLO", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "b2fc728c-e2f5-4a99-8899-eb7b34ef22d7", - "type": "container" - } - }, - "vessel": { - "data": null - }, - "location": { - "data": { - "id": "2a765fdd-c479-4345-b71d-c4ef839952e2", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - } - ] - } - }, - "Estimated Event": { - "value": { - "data": { - "id": "d7e04138-b59d-4c41-9d2d-251d95bedd6e", - "type": "webhook_notification", - "attributes": { - "id": "d7e04138-b59d-4c41-9d2d-251d95bedd6e", - "event": "shipment.estimated.arrival", - "delivery_status": "pending", - "created_at": "2020-09-11T21:25:34Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "b68bc6cb-2c37-43f6-889b-86a16b2b6fe6", - "type": "estimated_event" - } - }, - "webhook": { - "data": { - "id": "614eab61-ae3c-4d40-bbe9-41200a172691", - "type": "webhook" - } - } - } - }, - "included": [ - { - "id": "b68bc6cb-2c37-43f6-889b-86a16b2b6fe6", - "type": "estimated_event", - "attributes": { - "created_at": "2020-04-06T19:02:46-07:00", - "estimated_timestamp": "2020-04-09T19:02:46-07:00", - "voyage_number": "A1C", - "event": "shipment.estimated.arrival", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "715ed64b-6195-49f6-9407-1383a8088bfd", - "type": "shipment" - } - }, - "port": { - "data": { - "id": "ed4001a5-ad9d-43c3-883c-79354f422510", - "type": "port" - } - }, - "vessel": { - "data": { - "id": "ebf68c6c-9d0d-4383-aa41-e097009dfb4c", - "type": "vessel" - } - } - } - }, - { - "id": "ed4001a5-ad9d-43c3-883c-79354f422510", - "type": "port", - "attributes": { - "id": "ed4001a5-ad9d-43c3-883c-79354f422510", - "name": "Port of Oakland", - "code": "USOAK", - "state_abbr": "CA", - "city": "Oakland", - "country_code": "US", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "715ed64b-6195-49f6-9407-1383a8088bfd", - "type": "shipment", - "attributes": { - "created_at": "2020-04-06T19:02:46-07:00", - "bill_of_lading_number": "TE49DD6650B9", - "ref_numbers": [ - "REF-4A25EA" - ], - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": null, - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2020-04-13T19:02:46-07:00", - "pod_ata_at": null, - "pod_timezone": "America/Los_Angeles" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "1378c720-efe9-4562-a2ad-562002eb4b1d", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "ed4001a5-ad9d-43c3-883c-79354f422510", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "2508d879-4451-4d7f-ab23-92258b5df553", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "containers": { - "data": [] - } - }, - "links": { - "self": "/v2/shipments/715ed64b-6195-49f6-9407-1383a8088bfd" - } - } - ] - } - }, - "Transport Event": { - "value": { - "data": { - "id": "abec839a-48fe-4540-93d7-d3ea3d67bdbf", - "type": "webhook_notification", - "attributes": { - "id": "abec839a-48fe-4540-93d7-d3ea3d67bdbf", - "event": "container.transport.vessel_arrived", - "delivery_status": "pending", - "created_at": "2020-07-28T23:12:53Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "a6ecb8ab-98d6-4cab-8487-ce9dd7be082b", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "534d498b-8332-439a-accb-129dfd144ceb", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [] - } - } - }, - "included": [ - { - "id": "a6ecb8ab-98d6-4cab-8487-ce9dd7be082b", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_arrived", - "created_at": "2020-07-28T23:12:53Z", - "voyage_number": null, - "timestamp": "2020-07-28T23:12:53Z", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "1fc35241-4c8b-420d-803a-9e6661720a05", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "8c2f335a-b155-4021-87f0-9b040159a981", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "b381c692-8dad-4f04-873f-d9e567143335", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "f5a8a49f-d8b2-4d2a-8a43-0e4ff0ce7995", - "type": "port" - } - }, - "terminal": { - "data": { - "id": "26fede8d-2c6d-4bf5-98d6-5a86d30f17a9", - "type": "terminal" - } - } - } - }, - { - "id": "f5a8a49f-d8b2-4d2a-8a43-0e4ff0ce7995", - "type": "port", - "attributes": { - "id": "f5a8a49f-d8b2-4d2a-8a43-0e4ff0ce7995", - "name": "Port of Oakland", - "code": "USOAK", - "state_abbr": "CA", - "city": "Oakland", - "country_code": "US", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "1fc35241-4c8b-420d-803a-9e6661720a05", - "type": "shipment", - "attributes": { - "created_at": "2020-07-28T23:12:53Z", - "bill_of_lading_number": "TE491846459E", - "ref_numbers": [ - null - ], - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2020-07-15T23:12:53Z", - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2020-08-04T23:12:53Z", - "pod_ata_at": null, - "pod_timezone": "America/Los_Angeles" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "06564cb7-77d6-4e0e-8e4a-37756ca21bc9", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "f5a8a49f-d8b2-4d2a-8a43-0e4ff0ce7995", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "06f5d3bb-f258-4f1b-8c2f-db78248f6e29", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "containers": { - "data": [ - { - "id": "8c2f335a-b155-4021-87f0-9b040159a981", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/1fc35241-4c8b-420d-803a-9e6661720a05" - } - } - ] - } - }, - "Container Updated Event": { - "value": { - "data": { - "id": "416e293f-4423-47f7-abf3-1ae97054f41f", - "type": "webhook_notification", - "attributes": { - "id": "416e293f-4423-47f7-abf3-1ae97054f41f", - "event": "container.updated", - "delivery_status": "pending", - "created_at": "2020-06-04T22:03:09Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "fc48cb10-b7a8-47a4-a12f-89bce7434978", - "type": "container_updated_event" - } - }, - "webhook": { - "data": { - "id": "cda37836-aa40-455e-8b43-5fd74930c7f6", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [] - } - } - }, - "included": [ - { - "id": "fc48cb10-b7a8-47a4-a12f-89bce7434978", - "type": "container_updated_event", - "attributes": { - "changeset": { - "available_for_pickup": [ - false, - true - ], - "pod_terminal_holds": [ - null, - [ - { - "name": "customs", - "status": "hold", - "description": "CUST EXAM" - } - ] - ] - }, - "timestamp": "2020-06-04T22:03:09Z", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "container": { - "data": { - "id": "1445af31-991c-4d52-a183-6c3ea97cd6e8", - "type": "container" - } - }, - "terminal": { - "data": { - "id": "07db0258-1911-4acf-8e70-0cfe4b100f80", - "type": "terminal" - } - } - } - }, - { - "id": "1445af31-991c-4d52-a183-6c3ea97cd6e8", - "type": "container", - "attributes": { - "number": "GLDU1355602", - "seal_number": "431ac97412228532", - "created_at": "2020-05-04T22:03:09Z", - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": 55634, - "fees_at_pod_terminal": [], - "holds_at_pod_terminal": [], - "pickup_lfd": null, - "availability_known": true, - "available_for_pickup": null, - "pod_arrived_at": "2020-06-04T22:03:08Z", - "pod_discharged_at": "2020-06-04T22:03:08Z", - "final_destination_full_out_at": "2020-06-04T22:03:08Z", - "pod_full_out_at": null, - "empty_terminated_at": null, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": null, - "empty_terminated_timezone": null - }, - "relationships": { - "shipment": { - "data": null - } - } - }, - { - "id": "07db0258-1911-4acf-8e70-0cfe4b100f80", - "type": "terminal", - "attributes": { - "id": "07db0258-1911-4acf-8e70-0cfe4b100f80", - "nickname": "Denesik-Hintz", - "name": "Adams LLC Terminal", - "firms_code": "E005" - }, - "relationships": { - "port": { - "data": { - "id": "d8a92775-95f9-47be-a6d2-42542a32d5fc", - "type": "port" - } - } - } - } - ] - } - } - } - } - } - } - }, - "operationId": "get-webhook-notification-id", - "description": "\n", - "tags": [ - "Webhook Notifications" - ], - "parameters": [ - { - "schema": { - "type": "string" - }, - "in": "query", - "description": "Comma delimited list of relations to include.", - "name": "include" - } - ] - } - }, - "/webhook_notifications": { - "get": { - "summary": "List webhook notifications", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/webhook_notification" - } - }, - "links": { - "$ref": "#/components/schemas/links" - }, - "meta": { - "$ref": "#/components/schemas/meta" - }, - "included": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/webhook" - }, - { - "$ref": "#/components/schemas/tracking_request" - }, - { - "$ref": "#/components/schemas/transport_event" - }, - { - "$ref": "#/components/schemas/estimated_event" - } - ] - } - } - } - } - } - } - } - }, - "operationId": "get-webhook-notifications", - "description": "Return the list of webhook notifications. This can be useful for reconciling your data if your endpoint has been down. ", - "tags": [ - "Webhook Notifications" - ], - "parameters": [ - { - "schema": { - "type": "integer" - }, - "in": "query", - "name": "page[number]" - }, - { - "schema": { - "type": "integer" - }, - "in": "query", - "name": "page[size]" - }, - { - "schema": { - "type": "string" - }, - "in": "query", - "name": "include", - "description": "Comma delimited list of relations to include." - } - ] - } - }, - "/webhook_notifications/examples": { - "get": { - "summary": "Get webhook notification payload examples", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/webhook_notification" - } - }, - "links": { - "$ref": "#/components/schemas/links" - }, - "meta": { - "$ref": "#/components/schemas/meta" - }, - "included": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/webhook" - }, - { - "$ref": "#/components/schemas/tracking_request" - }, - { - "$ref": "#/components/schemas/transport_event" - }, - { - "$ref": "#/components/schemas/estimated_event" - } - ] - } - } - } - } - } - } - } - }, - "operationId": "get-webhook-notifications-example", - "description": "Returns an example payload as it would be sent to a webhook endpoint for the provided `event` ", - "tags": [ - "Webhook Notifications" - ], - "parameters": [ - { - "schema": { - "type": "string", - "enum": [ - "container.transport.vessel_arrived", - "container.transport.vessel_discharged", - "container.transport.vessel_loaded", - "container.transport.vessel_departed", - "container.transport.rail_departed", - "container.transport.rail_arrived", - "container.transport.rail_loaded", - "container.transport.rail_unloaded", - "container.transport.transshipment_arrived", - "container.transport.transshipment_discharged", - "container.transport.transshipment_loaded", - "container.transport.transshipment_departed", - "container.transport.feeder_arrived", - "container.transport.feeder_discharged", - "container.transport.feeder_loaded", - "container.transport.feeder_departed", - "container.transport.empty_out", - "container.transport.full_in", - "container.transport.full_out", - "container.transport.empty_in", - "container.transport.vessel_berthed", - "shipment.estimated.arrival", - "tracking_request.succeeded", - "tracking_request.failed", - "tracking_request.awaiting_manifest", - "tracking_request.tracking_stopped", - "container.created", - "container.updated", - "container.pod_terminal_changed", - "container.transport.arrived_at_inland_destination", - "container.transport.estimated.arrived_at_inland_destination", - "container.pickup_lfd.changed", - "container.transport.available" - ], - "example": "container.transport.full_out" - }, - "in": "query", - "name": "event", - "description": "The webhook notification event name you wish to see an example of" - } - ] - }, - "parameters": [] - }, - "/webhooks/ips": { - "get": { - "summary": "List webhook IPs", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "webhook_notification_ips": { - "type": "array", - "items": { - "type": "string", - "format": "ipv4" - } - }, - "last_updated": { - "type": "string", - "format": "date-time" - } - } - }, - "examples": { - "Example List of IPs": { - "value": { - "webhook_notification_ips": [ - "35.222.62.171", - "3.230.67.145", - "44.217.15.129" - ], - "last_updated": "2023-10-17T21:23:16Z" - } - } - } - } - } - } - }, - "operationId": "get-webhooks-ips", - "description": "Return the list of IPs used for sending webhook notifications. This can be useful for whitelisting the IPs on the firewall.", - "tags": [ - "Webhooks" - ], - "parameters": [] - } - }, - "/containers": { - "get": { - "summary": "List containers", - "tags": [ - "Containers" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/container" - } - }, - "included": { - "type": "array", - "items": { - "$ref": "#/components/schemas/shipment" - } - }, - "links": { - "$ref": "#/components/schemas/links" - }, - "meta": { - "$ref": "#/components/schemas/meta" - } - } - }, - "examples": { - "Example List of containers": { - "value": { - "data": [ - { - "id": "be0b247b-c144-4163-8919-cf9178930736", - "type": "container", - "attributes": { - "number": "TCLU6718159", - "seal_number": null, - "created_at": "2024-06-26T15:05:18Z", - "ref_numbers": [], - "pod_arrived_at": "2024-06-21T14:12:00Z", - "pod_discharged_at": "2024-06-23T00:19:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": 53502, - "pod_full_out_at": "2024-06-26T16:15:00Z", - "empty_terminated_at": null, - "terminal_checked_at": "2024-06-26T18:47:56Z", - "fees_at_pod_terminal": [], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "Community", - "pod_last_tracking_request_at": "2024-06-26T18:47:56Z", - "shipment_last_tracking_request_at": "2024-06-26T15:05:18Z", - "availability_known": true, - "pod_timezone": "America/New_York", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/New_York", - "pod_rail_carrier_scac": null, - "ind_rail_carrier_scac": null, - "pod_rail_loaded_at": null, - "pod_rail_departed_at": null, - "ind_eta_at": null, - "ind_ata_at": null, - "ind_rail_unloaded_at": null, - "ind_facility_lfd_on": null - }, - "relationships": { - "shipment": { - "data": { - "id": "cc8f8e43-d6a9-4edb-a8c0-d0ab03c113d3", - "type": "shipment" - } - }, - "pickup_facility": { - "data": null - }, - "pod_terminal": { - "data": { - "id": "a243bdf8-0da3-4056-a6a7-05fe8ab43999", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "fb53b35c-6a0c-4e22-b196-b623e8ba7db5", - "type": "transport_event" - }, - { - "id": "179897a3-04f5-450b-86e1-db57970c0248", - "type": "transport_event" - }, - { - "id": "a119b8e6-10c5-4967-8364-885f7dbf8e50", - "type": "transport_event" - }, - { - "id": "3372d58c-df89-46a2-b064-b308a9dc7040", - "type": "transport_event" - }, - { - "id": "800a3e59-8231-4e42-a80f-73c97cfb1be9", - "type": "transport_event" - }, - { - "id": "d466676b-0073-4b0c-89aa-d42486e9ed4f", - "type": "transport_event" - }, - { - "id": "85665836-5915-4cb9-ab78-b8487598cd0d", - "type": "transport_event" - }, - { - "id": "94cca22a-520a-4d19-a551-0554aceb3794", - "type": "transport_event" - }, - { - "id": "032e812f-d5e4-48af-8d79-2c2b41a07032", - "type": "transport_event" - }, - { - "id": "1b7dd74d-eff6-4e06-9a15-234295ce6fd5", - "type": "transport_event" - }, - { - "id": "b9f07b7e-1653-4209-b375-4588653d5275", - "type": "transport_event" - }, - { - "id": "a35f3c1b-ad35-4347-b0f2-9e08f0d4ca64", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "e26fc659-f79d-4cc0-8efc-5ce5e8444891", - "type": "raw_event" - }, - { - "id": "8820af5e-cb77-41c5-a897-906f2c56eb1e", - "type": "raw_event" - }, - { - "id": "a628a2c2-dab6-4b04-b3ae-d7ec99098a89", - "type": "raw_event" - }, - { - "id": "6ca157f9-3b58-4db5-8155-fc7b41a62611", - "type": "raw_event" - }, - { - "id": "6fb06390-4b0a-4c1f-9703-ec89927df7f3", - "type": "raw_event" - }, - { - "id": "26fe408d-e091-412a-a2fb-23a4d778b6b9", - "type": "raw_event" - }, - { - "id": "16490dd8-d79f-468a-91b1-dbb30bb45c85", - "type": "raw_event" - }, - { - "id": "ff3db923-a644-4706-8057-1bd53c95fbd5", - "type": "raw_event" - }, - { - "id": "3fd27ffd-9618-477f-b1a9-cbc179defefe", - "type": "raw_event" - }, - { - "id": "f92efdd3-f79c-4a1c-97ce-9a47588b525c", - "type": "raw_event" - }, - { - "id": "3b2a88ef-df0b-4e61-99c1-d4a175910111", - "type": "raw_event" - }, - { - "id": "9b367e33-9e43-488f-8217-081698adf40d", - "type": "raw_event" - }, - { - "id": "ee0915df-e2f8-46b0-acf1-816871ca142d", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "a9e52f3d-2fa9-467c-8deb-09e90dac2f0b", - "type": "container", - "attributes": { - "number": "TCLU2224327", - "seal_number": null, - "created_at": "2024-06-26T15:05:34Z", - "ref_numbers": [], - "pod_arrived_at": "2024-06-21T14:12:00Z", - "pod_discharged_at": "2024-06-23T17:21:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 20, - "equipment_height": "standard", - "weight_in_lbs": 44225, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": "2024-06-26T18:47:56Z", - "fees_at_pod_terminal": [], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "Yard", - "pod_last_tracking_request_at": "2024-06-26T18:47:56Z", - "shipment_last_tracking_request_at": "2024-06-26T15:05:34Z", - "availability_known": true, - "pod_timezone": "America/New_York", - "final_destination_timezone": "America/Chicago", - "empty_terminated_timezone": "America/Chicago", - "pod_rail_carrier_scac": "CSXT", - "ind_rail_carrier_scac": "CSXT", - "pod_rail_loaded_at": null, - "pod_rail_departed_at": null, - "ind_eta_at": "2024-07-02T14:20:00Z", - "ind_ata_at": null, - "ind_rail_unloaded_at": null, - "ind_facility_lfd_on": null - }, - "relationships": { - "shipment": { - "data": { - "id": "99f84294-3bda-4765-81b3-31765e6d2a24", - "type": "shipment" - } - }, - "pickup_facility": { - "data": { - "id": "e6fa9a01-511b-4f43-a7e1-d628315b84ef", - "type": "terminal" - } - }, - "pod_terminal": { - "data": { - "id": "a243bdf8-0da3-4056-a6a7-05fe8ab43999", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "4a5a04b7-8974-4c46-beaa-bf55004422c9", - "type": "transport_event" - }, - { - "id": "11c90391-aa9c-408b-b20e-daed8dd09586", - "type": "transport_event" - }, - { - "id": "d7f23c71-7084-4fbb-9fef-740f624182aa", - "type": "transport_event" - }, - { - "id": "c4c3537d-5c47-4623-afe0-edc0dd6f75c5", - "type": "transport_event" - }, - { - "id": "e4002e13-0a74-4bd4-9147-787bb21e2fda", - "type": "transport_event" - }, - { - "id": "57b5568f-d8a6-443d-b1c8-be5cd080e5ed", - "type": "transport_event" - }, - { - "id": "0771cff1-79bf-4eaa-9d9d-790cb433ce44", - "type": "transport_event" - }, - { - "id": "031021b9-7b39-41f7-bd45-26cc8cb799d2", - "type": "transport_event" - }, - { - "id": "9956448d-34d3-4c23-bcfd-19807eb4034f", - "type": "transport_event" - }, - { - "id": "f1caf6ea-3e92-4b68-bcea-556828301062", - "type": "transport_event" - }, - { - "id": "2dd558a8-fa07-454c-acf3-b53d072264af", - "type": "transport_event" - }, - { - "id": "83118511-d9ed-4b16-b323-5e685d9b266e", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "f256289f-219d-45f6-b727-56fb9c6bc433", - "type": "raw_event" - }, - { - "id": "c5581f60-ffb2-4ef0-a855-b70acd3b294f", - "type": "raw_event" - }, - { - "id": "3c6716f9-814c-4459-9bbb-753f010446b2", - "type": "raw_event" - }, - { - "id": "62c7b849-c99b-4e37-9861-105141cc0a4c", - "type": "raw_event" - }, - { - "id": "7db37d43-426a-4f84-82af-2957621ce466", - "type": "raw_event" - }, - { - "id": "d4342119-db56-46fc-8299-f573f5b52e73", - "type": "raw_event" - }, - { - "id": "ba159c3b-590c-43ff-bc04-b0354fd326f4", - "type": "raw_event" - }, - { - "id": "ec1be31a-17f1-4f6f-85ab-c74cb0dbe6cb", - "type": "raw_event" - }, - { - "id": "d12c4656-0d1f-4f30-a0fa-a2ee887741a8", - "type": "raw_event" - }, - { - "id": "146d56e2-b41d-4469-8202-0c7d7315e794", - "type": "raw_event" - }, - { - "id": "6b86b4d1-f85f-4c26-9fbc-3132a62f0fbc", - "type": "raw_event" - }, - { - "id": "bb2c9105-e421-4372-9265-e61b3fa54851", - "type": "raw_event" - }, - { - "id": "3a09c49f-c12e-49e9-b2de-b8dca2b3d608", - "type": "raw_event" - }, - { - "id": "54d91ebc-8f57-4764-b7c6-a3f4dc2459be", - "type": "raw_event" - }, - { - "id": "9e788c46-9431-41eb-baee-c8b14fb4f590", - "type": "raw_event" - }, - { - "id": "d3051802-8b6f-497d-ad16-fb35547c8662", - "type": "raw_event" - }, - { - "id": "af3090e7-c6d3-4d8e-88aa-9cd757102f9b", - "type": "raw_event" - }, - { - "id": "95daf204-d95d-4a89-bedf-358bedb8b3b8", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "8d1faeeb-3890-4fac-8659-cd13737b26f1", - "type": "container", - "attributes": { - "number": "CMAU0619052", - "seal_number": null, - "created_at": "2024-06-26T15:02:11Z", - "ref_numbers": [], - "pod_arrived_at": "2024-06-22T22:10:00Z", - "pod_discharged_at": "2024-06-23T20:38:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 20, - "equipment_height": "standard", - "weight_in_lbs": null, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": "2024-06-26T18:47:47Z", - "fees_at_pod_terminal": [], - "pickup_lfd": "2024-06-27T07:00:00Z", - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "Grounded", - "pod_last_tracking_request_at": "2024-06-26T18:47:47Z", - "shipment_last_tracking_request_at": "2024-06-26T15:02:11Z", - "availability_known": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": "Asia/Shanghai", - "empty_terminated_timezone": "Asia/Shanghai", - "pod_rail_carrier_scac": null, - "ind_rail_carrier_scac": null, - "pod_rail_loaded_at": null, - "pod_rail_departed_at": null, - "ind_eta_at": null, - "ind_ata_at": null, - "ind_rail_unloaded_at": null, - "ind_facility_lfd_on": null - }, - "relationships": { - "shipment": { - "data": { - "id": "f706cbea-3264-473d-8d26-af257f3bc1be", - "type": "shipment" - } - }, - "pickup_facility": { - "data": null - }, - "pod_terminal": { - "data": { - "id": "eaa2580c-5f5b-4198-85e4-821145d62098", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "1bb3a814-edab-403f-8ef2-a6d0966df423", - "type": "transport_event" - }, - { - "id": "19f197bf-444c-40ee-8478-6f02abe715a9", - "type": "transport_event" - }, - { - "id": "4c9223bb-0218-4175-8a2e-7bb99c40642a", - "type": "transport_event" - }, - { - "id": "432da964-6e99-45e9-b4b1-00b7be858591", - "type": "transport_event" - }, - { - "id": "b462b4d1-1e02-4037-af7f-6c8fa981f268", - "type": "transport_event" - }, - { - "id": "e3ca4a25-692f-474a-aa71-48fb9840aef1", - "type": "transport_event" - }, - { - "id": "014b9d1f-f033-4a3e-89f9-c57569883436", - "type": "transport_event" - }, - { - "id": "2cec71c9-721a-4060-993f-0ffcf01151cd", - "type": "transport_event" - }, - { - "id": "24941515-1b5e-4fca-87eb-092e56ed156a", - "type": "transport_event" - }, - { - "id": "0fd06bc0-1fda-467c-ab67-90a30c6d62ab", - "type": "transport_event" - }, - { - "id": "0bce915e-b9ba-42a0-a484-136266fe8b9a", - "type": "transport_event" - }, - { - "id": "582006aa-6547-4712-b964-5637aad839b4", - "type": "transport_event" - }, - { - "id": "11458420-b354-4288-a275-7572d3c60e33", - "type": "transport_event" - }, - { - "id": "5c900fa1-11bf-4f96-89e7-b12f6a98edc4", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "999d7de7-eaed-4313-85df-772a6d24a85e", - "type": "raw_event" - }, - { - "id": "ac9c2780-240d-443f-87f4-95465fa5447b", - "type": "raw_event" - }, - { - "id": "a0ee3724-91c3-4b78-af32-2f16e8c2d600", - "type": "raw_event" - }, - { - "id": "79725b1f-19f7-4fc3-8c69-586e237c1719", - "type": "raw_event" - }, - { - "id": "4f07f257-ea4f-4e61-94ed-a01598899020", - "type": "raw_event" - }, - { - "id": "41612acb-b3d4-495f-9138-f34105851d21", - "type": "raw_event" - }, - { - "id": "84ffdd37-ec39-404a-9b68-d72d8fb96d48", - "type": "raw_event" - }, - { - "id": "7e9d8a6d-5339-4266-a6fb-22c28f41149f", - "type": "raw_event" - }, - { - "id": "2359b787-b218-42dc-b9a5-84b608aee671", - "type": "raw_event" - }, - { - "id": "ea33303e-0e48-4442-9886-0dfe38b726b5", - "type": "raw_event" - }, - { - "id": "3689d013-8525-418b-92ed-95ec684130b4", - "type": "raw_event" - }, - { - "id": "a64f7f7e-a6fa-4913-aba0-b28ba189d68b", - "type": "raw_event" - }, - { - "id": "c264a859-4fcb-4fab-95c0-29be99ec54a4", - "type": "raw_event" - }, - { - "id": "2aabf25c-9a3e-44bc-b6c8-ed7b1e3c3630", - "type": "raw_event" - }, - { - "id": "6ae70038-02d7-425a-99c1-8761c69a9033", - "type": "raw_event" - }, - { - "id": "c30021d8-93e9-4bfd-a978-e9e24de148c8", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "853f1794-9b94-4118-9970-4e28e549440d", - "type": "container", - "attributes": { - "number": "TGHU6578122", - "seal_number": null, - "created_at": "2024-06-26T15:08:30Z", - "ref_numbers": [], - "pod_arrived_at": "2024-06-23T21:58:00Z", - "pod_discharged_at": "2024-06-24T02:28:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [], - "available_for_pickup": false, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": 8898, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": "2024-06-26T18:47:47Z", - "fees_at_pod_terminal": [], - "pickup_lfd": "2024-06-27T07:00:00Z", - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "Wheeled", - "pod_last_tracking_request_at": "2024-06-26T18:47:46Z", - "shipment_last_tracking_request_at": "2024-06-26T15:08:30Z", - "availability_known": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": "America/New_York", - "empty_terminated_timezone": "America/New_York", - "pod_rail_carrier_scac": "BNSF", - "ind_rail_carrier_scac": "CSXT", - "pod_rail_loaded_at": null, - "pod_rail_departed_at": null, - "ind_eta_at": "2024-07-07T08:00:00Z", - "ind_ata_at": null, - "ind_rail_unloaded_at": null, - "ind_facility_lfd_on": null - }, - "relationships": { - "shipment": { - "data": { - "id": "f3cfe624-706e-4a0c-89d5-140980d986fd", - "type": "shipment" - } - }, - "pickup_facility": { - "data": { - "id": "7e4557b9-cc5a-4298-aaec-1a32e90202c9", - "type": "terminal" - } - }, - "pod_terminal": { - "data": { - "id": "eaa2580c-5f5b-4198-85e4-821145d62098", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "94f687d0-dc6b-4342-8710-cb98bc98716e", - "type": "transport_event" - }, - { - "id": "a8d0a842-95fe-4c12-a80e-bd12e87a1421", - "type": "transport_event" - }, - { - "id": "c1f1a186-3737-41ca-ae2b-f79a17519991", - "type": "transport_event" - }, - { - "id": "7fcc5496-d402-4b1c-a6c5-8514e6433070", - "type": "transport_event" - }, - { - "id": "ab7cd84a-edc5-476d-a1c4-190011582314", - "type": "transport_event" - }, - { - "id": "68d91384-3eb3-4d21-ac7a-c1f688f649c2", - "type": "transport_event" - }, - { - "id": "5a702f46-4356-4570-bd2e-2b8adab5ba3e", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "a4b7e4b6-5227-4f34-9e91-9e75223798ae", - "type": "raw_event" - }, - { - "id": "4f3e3c7b-d1e6-4fde-b95e-ce9a8835445c", - "type": "raw_event" - }, - { - "id": "39e8570d-1a8f-4e10-8d2e-ac930abc5971", - "type": "raw_event" - }, - { - "id": "b8015a72-9741-4fbf-8adc-d30b87de6aa3", - "type": "raw_event" - }, - { - "id": "61512b1a-a94e-4cac-8bab-763588dbbddf", - "type": "raw_event" - }, - { - "id": "c0bf87f2-37c3-4895-90b0-9f97ac4b5c13", - "type": "raw_event" - }, - { - "id": "3856c7e7-f832-42ca-873b-096952599e29", - "type": "raw_event" - }, - { - "id": "485be998-0861-4d10-8a60-f66d27eb46a7", - "type": "raw_event" - }, - { - "id": "29cde194-bbd9-40c2-adba-5dcb1514f5fc", - "type": "raw_event" - } - ] - } - } - }, - { - "id": "681d713d-bcd6-4303-b082-b9f893e7d1d9", - "type": "container", - "attributes": { - "number": "CSNU8439129", - "seal_number": null, - "created_at": "2024-06-26T15:02:32Z", - "ref_numbers": [], - "pod_arrived_at": "2024-06-23T21:58:00Z", - "pod_discharged_at": "2024-06-26T04:30:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [], - "available_for_pickup": true, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": 40488, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": "2024-06-26T18:47:47Z", - "fees_at_pod_terminal": [], - "pickup_lfd": "2024-07-01T07:00:00Z", - "pickup_appointment_at": "2024-06-28T15:00:00Z", - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "Grounded", - "pod_last_tracking_request_at": "2024-06-26T18:47:46Z", - "shipment_last_tracking_request_at": "2024-06-26T15:02:32Z", - "availability_known": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": "America/Chicago", - "empty_terminated_timezone": "America/Chicago", - "pod_rail_carrier_scac": "BNSF", - "ind_rail_carrier_scac": "BNSF", - "pod_rail_loaded_at": null, - "pod_rail_departed_at": null, - "ind_eta_at": "2024-07-04T22:00:00Z", - "ind_ata_at": null, - "ind_rail_unloaded_at": null, - "ind_facility_lfd_on": null - }, - "relationships": { - "shipment": { - "data": { - "id": "edd626cf-b0b5-4679-8a6c-80c8e9fe7698", - "type": "shipment" - } - }, - "pickup_facility": { - "data": { - "id": "572b372f-21c7-4403-8fb0-948377c74642", - "type": "terminal" - } - }, - "pod_terminal": { - "data": { - "id": "eaa2580c-5f5b-4198-85e4-821145d62098", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "9d19125a-2944-442c-8132-bf0d83670e5c", - "type": "transport_event" - }, - { - "id": "b2ff0b47-3151-41e2-8c46-7f95cdbe4167", - "type": "transport_event" - }, - { - "id": "e52feaa3-7a50-4570-a2ea-bf06f955ce23", - "type": "transport_event" - }, - { - "id": "c56d95e5-774f-432c-b6f4-c53967f07292", - "type": "transport_event" - }, - { - "id": "9b1d8b48-e870-46fa-bc46-10f9b30c64d4", - "type": "transport_event" - }, - { - "id": "3e1d3571-6b24-4ad2-a071-4e70d59af521", - "type": "transport_event" - }, - { - "id": "60f5eefe-13d5-4f85-9b65-d13b4c67115a", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "2af51127-0971-4741-9b97-ea1b338e3a7c", - "type": "raw_event" - }, - { - "id": "4472ded6-eb69-4f21-8666-9a4f2342dfeb", - "type": "raw_event" - }, - { - "id": "ce238754-d5fd-4dc8-9f55-2ac6efdfbb5e", - "type": "raw_event" - }, - { - "id": "5cd3b5ef-843f-4f60-87ce-5beaeea86f7b", - "type": "raw_event" - }, - { - "id": "55291737-98b6-403c-9424-1605e6e01007", - "type": "raw_event" - }, - { - "id": "cdc55ea8-a075-45b3-9570-ade6fb2f0d94", - "type": "raw_event" - }, - { - "id": "c0ab5406-4e10-4fdc-85a4-e17ce8d956f5", - "type": "raw_event" - }, - { - "id": "965f0172-1c20-4342-a44a-7be0e594ff76", - "type": "raw_event" - }, - { - "id": "db178011-c795-42a4-9537-b4e77ffb4f98", - "type": "raw_event" - } - ] - } - } - } - ], - "meta": { - "size": 5, - "total": 59229 - }, - "links": { - "self": "https://api.terminal49.com/v2/containers?page[size]=5", - "current": "https://api.terminal49.com/v2/containers?page[number]=1&page[size]=5", - "next": "https://api.terminal49.com/v2/containers?page[number]=2&page[size]=5", - "last": "https://api.terminal49.com/v2/containers?page[number]=11846&page[size]=5" - } - } - } - } - } - } - } - }, - "operationId": "get-containers", - "description": "Returns a list of container. The containers are returned sorted by creation date, with the most recently refreshed containers appearing first.\n\nThis API will return all containers associated with the account.", - "parameters": [ - { - "schema": { - "type": "integer", - "default": 1 - }, - "in": "query", - "name": "page[number]" - }, - { - "schema": { - "type": "integer", - "default": 30 - }, - "in": "query", - "name": "page[size]", - "description": "" - }, - { - "schema": { - "type": "string" - }, - "in": "query", - "name": "include", - "description": "Comma delimited list of relations to include" - }, - { - "schema": { - "type": "integer" - }, - "in": "query", - "name": "terminal_checked_before", - "description": "Number of seconds in which containers were refreshed" - } - ] - }, - "patch": { - "summary": "Edit a container", - "operationId": "patch-containers-id", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/container" - } - } - } - } - } - } - }, - "description": "Update a container", - "tags": [ - "Containers" - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "object", - "required": [ - "attributes", - "type" - ], - "properties": { - "attributes": { - "type": "object", - "properties": { - "ref_numbers": { - "type": "array", - "items": { - "type": "string", - "example": "REF-12345" - } - } - } - } - } - } - } - } - } - } - } - } - }, - "/containers/{id}": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "get": { - "summary": "Get a container", - "tags": [ - "Containers" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/container" - }, - "included": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/shipment" - }, - { - "$ref": "#/components/schemas/terminal" - }, - { - "$ref": "#/components/schemas/transport_event" - } - ] - } - } - } - }, - "examples": { - "Example Container": { - "value": { - "data": { - "id": "55a700e4-7005-45a9-92fd-1ff38641dbd9", - "type": "container", - "attributes": { - "number": "CAIU7432986", - "seal_number": null, - "created_at": "2024-06-26T15:05:21Z", - "ref_numbers": [], - "pod_arrived_at": null, - "pod_discharged_at": "2024-06-22T04:00:00Z", - "final_destination_full_out_at": null, - "holds_at_pod_terminal": [], - "available_for_pickup": true, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": null, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": "2024-06-26T17:51:12Z", - "fees_at_pod_terminal": [], - "pickup_lfd": "2024-07-07T04:00:00Z", - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": "Yard - Y0709A", - "pod_last_tracking_request_at": "2024-06-26T17:51:12Z", - "shipment_last_tracking_request_at": "2024-06-26T15:05:20Z", - "availability_known": true, - "pod_timezone": "America/New_York", - "final_destination_timezone": "US/Eastern", - "empty_terminated_timezone": "US/Eastern", - "pod_rail_carrier_scac": "CSXT", - "ind_rail_carrier_scac": "CSXT", - "pod_rail_loaded_at": null, - "pod_rail_departed_at": null, - "ind_eta_at": null, - "ind_ata_at": null, - "ind_rail_unloaded_at": null, - "ind_facility_lfd_on": null - }, - "relationships": { - "shipment": { - "data": { - "id": "02b1bd6f-407c-45bb-8645-06e7ee34e7e3", - "type": "shipment" - } - }, - "pickup_facility": { - "data": null - }, - "pod_terminal": { - "data": { - "id": "b859f5c3-8515-41da-bf20-39c0a5ada887", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "45b542cb-332b-4684-b915-42e3a0759823", - "type": "transport_event" - }, - { - "id": "174ed528-a1a9-4002-aef0-f2c9369199da", - "type": "transport_event" - }, - { - "id": "7a2f30a6-a756-4c14-9477-fbfc1c7fe2f8", - "type": "transport_event" - }, - { - "id": "e7365004-175a-46e8-96cd-dbed0f3daf21", - "type": "transport_event" - }, - { - "id": "7c567bf3-7f01-4a3d-a176-eaa1f7165585", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "2956f71c-bfb9-4e49-b9e2-1b4d53c74cac", - "type": "raw_event" - }, - { - "id": "391e0eda-65b5-4fc3-a53d-25ecd9570259", - "type": "raw_event" - }, - { - "id": "74810c04-6c8a-4194-8cff-52936584a965", - "type": "raw_event" - }, - { - "id": "4b1500e2-b23b-4896-87bd-c38b1d16f385", - "type": "raw_event" - }, - { - "id": "8b9a7d88-720a-4304-8c1e-a3336e39f481", - "type": "raw_event" - }, - { - "id": "bf1f59c5-5dd8-4013-87f9-d7056bc87114", - "type": "raw_event" - } - ] - } - } - }, - "links": { - "self": "https://api.terminal49.com/v2/containers/55a700e4-7005-45a9-92fd-1ff38641dbd9" - } - } - } - } - } - } - } - }, - "operationId": "get-containers-id", - "description": "Retrieves the details of a container.", - "parameters": [ - { - "schema": { - "type": "string" - }, - "in": "query", - "name": "include", - "description": "Comma delimited list of relations to include" - } - ] - } - }, - "/containers/{id}/raw_events": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "get": { - "summary": "Get a container's raw events", - "tags": [ - "Containers" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/raw_event" - } - } - } - }, - "examples": { - "Example Raw Events": { - "value": { - "data": [ - { - "id": "ca6b760f-13e9-4bf6-ab49-3cf2e40757fb", - "type": "raw_event", - "attributes": { - "timestamp": "2020-03-03T00:00:00Z", - "estimated": false, - "actual_on": "2020-03-03", - "estimated_at": null, - "actual_at": null, - "event": "empty_out", - "index": 0, - "original_event": "Truck Gate out empty", - "created_at": "2020-04-18T00:18:27Z", - "voyage_number": null, - "location_name": "Oakland", - "location_locode": null, - "vessel_name": null, - "vessel_imo": null, - "timezone": null - }, - "relationships": { - "location": { - "data": null - }, - "vessel": { - "data": null - } - } - }, - { - "id": "bcdfc796-0570-4c85-9336-d6c7d0da02d2", - "type": "raw_event", - "attributes": { - "timestamp": "2020-03-09T00:00:00Z", - "estimated": false, - "actual_on": "2020-03-09", - "estimated_at": null, - "actual_at": null, - "event": "full_in", - "index": 1, - "original_event": "Truck Arrival in", - "created_at": "2020-04-18T00:18:27Z", - "voyage_number": null, - "location_name": null, - "location_locode": null, - "vessel_name": null, - "vessel_imo": null, - "timezone": null - }, - "relationships": { - "location": { - "data": null - }, - "vessel": { - "data": null - } - } - }, - { - "id": "a4ff01b0-b374-4123-ae65-3dc0c7ea41ea", - "type": "raw_event", - "attributes": { - "timestamp": "2020-03-14T00:00:00Z", - "estimated": false, - "actual_on": "2020-03-14", - "estimated_at": null, - "actual_at": null, - "event": "vessel_loaded", - "index": 2, - "original_event": "Vessel Loaded", - "created_at": "2020-04-18T00:18:27Z", - "voyage_number": "FA009R", - "location_name": null, - "location_locode": null, - "vessel_name": "MSC FAITH", - "vessel_imo": null, - "timezone": null - }, - "relationships": { - "location": { - "data": null - }, - "vessel": { - "data": { - "id": "4b473d0e-7073-4171-8b5b-15e71e9e13cc", - "type": "vessel" - } - } - } - }, - { - "id": "ca5862ef-6e27-4245-a281-0cec6bbe1fb7", - "type": "raw_event", - "attributes": { - "timestamp": "2020-03-15T00:00:00Z", - "estimated": false, - "actual_on": "2020-03-15", - "estimated_at": null, - "actual_at": null, - "event": "vessel_departed", - "index": 3, - "original_event": "Vessel departed", - "created_at": "2020-04-18T00:18:27Z", - "voyage_number": "FA009R", - "location_name": null, - "location_locode": null, - "vessel_name": "MSC FAITH", - "vessel_imo": null, - "timezone": null - }, - "relationships": { - "location": { - "data": null - }, - "vessel": { - "data": { - "id": "4b473d0e-7073-4171-8b5b-15e71e9e13cc", - "type": "vessel" - } - } - } - }, - { - "id": "f47a903e-e6d1-41c5-aec6-8401b2abf297", - "type": "raw_event", - "attributes": { - "timestamp": "2020-03-25T00:00:00Z", - "estimated": false, - "actual_on": "2020-03-25", - "estimated_at": null, - "actual_at": null, - "event": "transshipment_arrived", - "index": 4, - "original_event": "Vessel arrived", - "created_at": "2020-04-18T00:18:27Z", - "voyage_number": "FA009R", - "location_name": null, - "location_locode": null, - "vessel_name": "MSC FAITH", - "vessel_imo": null, - "timezone": null - }, - "relationships": { - "location": { - "data": null - }, - "vessel": { - "data": { - "id": "4b473d0e-7073-4171-8b5b-15e71e9e13cc", - "type": "vessel" - } - } - } - }, - { - "id": "72a1a13b-a2e0-4ac0-851d-eec41e9e9087", - "type": "raw_event", - "attributes": { - "timestamp": "2020-03-25T00:00:00Z", - "estimated": false, - "actual_on": "2020-03-25", - "estimated_at": null, - "actual_at": null, - "event": "transshipment_discharged", - "index": 5, - "original_event": "Vessel Discharged", - "created_at": "2020-04-18T00:18:27Z", - "voyage_number": "FA009R", - "location_name": null, - "location_locode": null, - "vessel_name": "MSC FAITH", - "vessel_imo": null, - "timezone": null - }, - "relationships": { - "location": { - "data": null - }, - "vessel": { - "data": { - "id": "4b473d0e-7073-4171-8b5b-15e71e9e13cc", - "type": "vessel" - } - } - } - }, - { - "id": "cd91f0cf-ee73-4c47-b99f-63245cb5bc96", - "type": "raw_event", - "attributes": { - "timestamp": "2020-04-07T00:00:00Z", - "estimated": false, - "actual_on": "2020-04-07", - "estimated_at": null, - "actual_at": null, - "event": "transshipment_loaded", - "index": 6, - "original_event": "Vessel Loaded", - "created_at": "2020-04-18T00:18:27Z", - "voyage_number": "15W10", - "location_name": null, - "location_locode": null, - "vessel_name": "SINGAPORE EXPRESS", - "vessel_imo": null, - "timezone": null - }, - "relationships": { - "location": { - "data": null - }, - "vessel": { - "data": { - "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", - "type": "vessel" - } - } - } - }, - { - "id": "561dbb7e-c3ab-4e63-b09b-957878b1425f", - "type": "raw_event", - "attributes": { - "timestamp": "2020-04-07T00:00:00Z", - "estimated": false, - "actual_on": "2020-04-07", - "estimated_at": null, - "actual_at": null, - "event": "transshipment_departed", - "index": 7, - "original_event": "Vessel departed", - "created_at": "2020-04-18T00:18:27Z", - "voyage_number": "15W10", - "location_name": null, - "location_locode": null, - "vessel_name": "SINGAPORE EXPRESS", - "vessel_imo": null, - "timezone": null - }, - "relationships": { - "location": { - "data": null - }, - "vessel": { - "data": { - "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", - "type": "vessel" - } - } - } - }, - { - "id": "551711a6-62ad-4205-8da2-00e0c0cbd2db", - "type": "raw_event", - "attributes": { - "timestamp": "2020-04-12T00:00:00Z", - "estimated": false, - "actual_on": "2020-04-12", - "estimated_at": null, - "actual_at": null, - "event": "vessel_arrived", - "index": 8, - "original_event": "Vessel arrived", - "created_at": "2020-04-18T00:18:27Z", - "voyage_number": "15W10", - "location_name": null, - "location_locode": null, - "vessel_name": "SINGAPORE EXPRESS", - "vessel_imo": null, - "timezone": null - }, - "relationships": { - "location": { - "data": null - }, - "vessel": { - "data": { - "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", - "type": "vessel" - } - } - } - }, - { - "id": "f4027470-75ca-4e2a-b4f0-47654a25ac48", - "type": "raw_event", - "attributes": { - "timestamp": "2020-04-13T00:00:00Z", - "estimated": false, - "actual_on": "2020-04-13", - "estimated_at": null, - "actual_at": null, - "event": "vessel_discharged", - "index": 9, - "original_event": "Vessel Discharged", - "created_at": "2020-04-18T00:18:27Z", - "voyage_number": "15W10", - "location_name": null, - "location_locode": null, - "vessel_name": "SINGAPORE EXPRESS", - "vessel_imo": null, - "timezone": null - }, - "relationships": { - "location": { - "data": null - }, - "vessel": { - "data": { - "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", - "type": "vessel" - } - } - } - }, - { - "id": "50f11e4f-411e-48e2-8141-64226500df9c", - "type": "raw_event", - "attributes": { - "timestamp": "2020-04-14T00:00:00Z", - "estimated": false, - "actual_on": "2020-04-14", - "estimated_at": null, - "actual_at": null, - "event": "full_out", - "index": 10, - "original_event": "Truck Departure from", - "created_at": "2020-04-18T00:18:27Z", - "voyage_number": null, - "location_name": null, - "location_locode": null, - "vessel_name": null, - "vessel_imo": null, - "timezone": null - }, - "relationships": { - "location": { - "data": null - }, - "vessel": { - "data": null - } - } - }, - { - "id": "49aea23c-b8c5-4a97-b133-f7a9723fa1b4", - "type": "raw_event", - "attributes": { - "timestamp": "2020-04-15T00:00:00Z", - "estimated": false, - "actual_on": "2020-04-15", - "estimated_at": null, - "actual_at": null, - "event": "empty_in", - "index": 11, - "original_event": "Truck Gate in empty", - "created_at": "2020-04-18T00:18:27Z", - "voyage_number": null, - "location_name": null, - "location_locode": null, - "vessel_name": null, - "vessel_imo": null, - "timezone": null - }, - "relationships": { - "location": { - "data": null - }, - "vessel": { - "data": null - } - } - } - ], - "included": [ - { - "id": "4b473d0e-7073-4171-8b5b-15e71e9e13cc", - "type": "vessel", - "attributes": { - "name": "MSC FAITH", - "imo": null, - "mmsi": "636019213", - "latitude": 70.22625823437389, - "longitude": 45.06279126658865, - "nautical_speed_knots": 100, - "navigational_heading_degrees": 1, - "position_timestamp": "2023-06-05T19:46:18Z" - } - }, - { - "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", - "type": "vessel", - "attributes": { - "name": "SINGAPORE EXPRESS", - "imo": null, - "mmsi": "477300500", - "latitude": 70.22625823437389, - "longitude": 45.06279126658865, - "nautical_speed_knots": 100, - "navigational_heading_degrees": 1, - "position_timestamp": "2023-06-05T19:46:18Z" - } - } - ] - } - } - } - } - } - } - }, - "operationId": "get-containers-id-raw_events", - "description": "#### Deprecation warning\nThe `raw_events` endpoint is provided as-is.\n\n For past events we recommend consuming `transport_events`.\n\n---\nGet a list of past and future (estimated) milestones for a container as reported by the carrier. Some of the data is normalized even though the API is called raw_events. \n\nNormalized attributes: `event` and `timestamp` timestamp. Not all of the `event` values have been normalized. You can expect the the events related to container movements to be normalized but there are cases where events are not normalized. \n\nFor past historical events we recommend consuming `transport_events`. Although there are fewer events here those events go through additional vetting and normalization to avoid false positives and get you correct data.", - "deprecated": true - } - }, - "/containers/{id}/transport_events": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "get": { - "summary": "Get a container's transport events", - "tags": [ - "Containers" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/transport_event" - } - }, - "included": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/shipment" - }, - { - "$ref": "#/components/schemas/container" - }, - { - "$ref": "#/components/schemas/port" - }, - { - "$ref": "#/components/schemas/metro_area" - }, - { - "$ref": "#/components/schemas/terminal" - }, - { - "$ref": "#/components/schemas/rail_terminal" - }, - { - "$ref": "#/components/schemas/vessel" - } - ] - } - }, - "links": { - "$ref": "#/components/schemas/links" - }, - "meta": { - "$ref": "#/components/schemas/meta" - } - } - }, - "examples": { - "Example transport events": { - "value": { - "data": [ - { - "id": "efc3f3c1-cdc2-4a7d-a176-762ddec107b8", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_loaded", - "created_at": "2021-01-05T08:41:12Z", - "voyage_number": "15W10", - "timestamp": null, - "location_locode": "CLSAI", - "timezone": "America/Santiago" - }, - "relationships": { - "shipment": { - "data": { - "id": "06264731-503e-498e-bc76-f90b87b31562", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "0ad2cf2b-e694-4ccc-9cd2-40af0d1fa1b5", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - }, - { - "id": "951058bd-2c3b-4bcc-94e1-9be2526b9687", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_departed", - "created_at": "2021-01-05T08:41:11Z", - "voyage_number": "15W10", - "timestamp": null, - "location_locode": "CLSAI", - "timezone": "America/Santiago" - }, - "relationships": { - "shipment": { - "data": { - "id": "06264731-503e-498e-bc76-f90b87b31562", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "0ad2cf2b-e694-4ccc-9cd2-40af0d1fa1b5", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - }, - { - "id": "69af6795-56c2-4157-9a87-afd761cc85a0", - "type": "transport_event", - "attributes": { - "event": "container.transport.full_out", - "created_at": "2020-05-14T00:05:41Z", - "voyage_number": null, - "timestamp": "2020-04-14T00:00:00Z", - "location_locode": "USOAK", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "06264731-503e-498e-bc76-f90b87b31562", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", - "type": "container" - } - }, - "vessel": { - "data": null - }, - "location": { - "data": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - }, - { - "id": "68c3c29a-504a-4dbb-ad27-7194ef42d484", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_discharged", - "created_at": "2020-05-14T00:05:41Z", - "voyage_number": "15W10", - "timestamp": "2020-04-13T00:00:00Z", - "location_locode": "USOAK", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "06264731-503e-498e-bc76-f90b87b31562", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port" - } - }, - "terminal": { - "data": { - "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", - "type": "terminal" - } - } - } - }, - { - "id": "03349405-a9be-4f3e-abde-28f2cb3922bd", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_arrived", - "created_at": "2020-05-14T00:05:41Z", - "voyage_number": "15W10", - "timestamp": "2020-04-13T01:24:00Z", - "location_locode": "USOAK", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "06264731-503e-498e-bc76-f90b87b31562", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", - "type": "port" - } - }, - "terminal": { - "data": { - "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", - "type": "terminal" - } - } - } - }, - { - "id": "ba9f85b4-658d-4f23-9308-635964df8037", - "type": "transport_event", - "attributes": { - "event": "container.transport.empty_in", - "created_at": "2020-05-14T00:05:42Z", - "voyage_number": null, - "timestamp": "2020-04-15T00:00:00Z", - "location_locode": null, - "timezone": null - }, - "relationships": { - "shipment": { - "data": { - "id": "06264731-503e-498e-bc76-f90b87b31562", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", - "type": "container" - } - }, - "vessel": { - "data": null - }, - "location": { - "data": null - }, - "terminal": { - "data": null - } - } - } - ], - "links": { - "self": "https://api.terminal49.com/v2/containers/eeafd337-72b5-4e5c-87cb-9ef83fa99cf4/transport_events", - "current": "https://api.terminal49.com/v2/containers/eeafd337-72b5-4e5c-87cb-9ef83fa99cf4/transport_events?page[number]=1" - } - } - } - } - } - } - } - }, - "operationId": "get-containers-id-transport_events", - "description": "Get a list of past transport events (canonical) for a container. All data has been normalized across all carriers. These are a verified subset of the raw events may also be sent as Webhook Notifications to a webhook endpoint.\n\nThis does not provide any estimated future events. See `container/:id/raw_events` endpoint for that. ", - "parameters": [ - { - "schema": { - "type": "string" - }, - "in": "query", - "name": "include", - "description": "Comma delimited list of relations to include" - } - ] - } - }, - "/containers/{id}/route": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "get": { - "summary": "Get container route", - "tags": [ - "Containers", - "Routing (Paid)" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/route" - }, - "included": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/port" - }, - { - "$ref": "#/components/schemas/vessel" - }, - { - "$ref": "#/components/schemas/route_location" - }, - { - "$ref": "#/components/schemas/shipment" - } - ] - } - } - } - } - } - } - }, - "403": { - "description": "Forbidden - Routing data feature is not enabled for this account", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "items": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "403" - }, - "title": { - "type": "string", - "example": "Forbidden" - }, - "detail": { - "type": "string", - "example": "Routing data feature is not enabled for this account" - } - } - } - } - } - } - } - } - } - }, - "operationId": "get-containers-id-route", - "description": "Retrieves the route details from the port of lading to the port of discharge, including transshipments. This is a paid feature. Please contact sales@terminal49.com." - } - }, - "/containers/{id}/refresh": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "patch": { - "summary": "Refresh container", - "operationId": "patch-containers-id-refresh", - "tags": [ - "Containers" - ], - "description": "Schedules the container to be refreshed immediately from all relevant sources.

To be alerted of updates you should subscribe to the [relevant webhooks](/api-docs/in-depth-guides/webhooks). This endpoint is limited to 10 requests per minute.This is a paid feature. Please contact sales@terminal49.com.", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Started refresh for Shipping line, Terminal, Rail" - } - } - }, - "examples": { - "Refresh response": { - "value": { - "message": "Started refresh for Shipping line, Terminal, Rail" - } - } - } - } - } - }, - "403": { - "description": "Forbidden - This API endpoint is not enabled for your account. Please contact support@terminal49.com", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "items": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "403" - }, - "title": { - "type": "string", - "example": "API access not enabled" - }, - "detail": { - "type": "string", - "example": "This API endpoint is not enabled for your account. Please contact support@terminal49.com" - } - } - } - } - } - } - } - } - }, - "429": { - "description": "Too Many Requests - You've hit the refresh limit. Please try again in a minute.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "items": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "429" - }, - "title": { - "type": "string", - "example": "Too Many Requests" - }, - "detail": { - "type": "string", - "example": "You've hit the refresh limit. Please try again in a minute." - } - } - } - } - } - } - } - }, - "headers": { - "Retry-After": { - "description": "Number of seconds to wait before making another request", - "schema": { - "type": "integer", - "example": 60 - } - } - } - } - } - } - }, - "/shipping_lines": { - "get": { - "summary": "Shipping Lines", - "tags": [ - "Shipping Lines" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/shipping_line" - } - }, - "links": { - "$ref": "#/components/schemas/links" - } - } - } - } - } - } - }, - "operationId": "get-shipping_lines", - "description": "Return a list of shipping lines supported by Terminal49. \nN.B. There is no pagination for this endpoint." - } - }, - "/shipping_lines/{id}": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "get": { - "summary": "Get a single shipping line", - "tags": [ - "Shipping Lines" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/shipping_line" - } - } - } - } - } - } - }, - "operationId": "get-shipping_lines-id", - "description": "Return the details of a single shipping line." - } - }, - "/metro_areas/{id}": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "get": { - "summary": "Get a metro area using the un/locode or the id", - "tags": [ - "Metro Areas" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/metro_area" - } - } - } - } - } - } - }, - "operationId": "get-metro-area-id", - "description": "Return the details of a single metro area." - } - }, - "/ports/{id}": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "get": { - "summary": "Get a port using the locode or the id", - "tags": [ - "Ports" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/port" - } - } - } - } - } - } - }, - "operationId": "get-port-id", - "description": "Return the details of a single port." - } - }, - "/vessels/{id}": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - }, - { - "schema": { - "type": "string", - "format": "date-time", - "example": "2025-05-20T00:00:00Z" - }, - "name": "show_positions[from_timestamp]", - "in": "query", - "description": "ISO 8601 timestamp to filter positions from. 7 days by default.", - "required": false - }, - { - "schema": { - "type": "string", - "format": "date-time", - "example": "2025-05-24T00:00:00Z" - }, - "name": "show_positions[to_timestamp]", - "in": "query", - "description": "ISO 8601 timestamp to filter positions up to. Current time by default.", - "required": false - } - ], - "get": { - "summary": "Get a vessel using the id", - "tags": [ - "Vessels" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/vessel" - } - } - } - } - } - }, - "403": { - "description": "Forbidden - Feature not enabled", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "items": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "403" - }, - "source": { - "type": "object", - "nullable": true - }, - "title": { - "type": "string", - "example": "Forbidden" - }, - "detail": { - "type": "string", - "example": "Routing data feature is not enabled for this account" - } - } - } - } - } - } - } - } - } - }, - "operationId": "get-vessels-id", - "description": "Returns a vessel by id. `show_positions` is a paid feature. Please contact sales@terminal49.com." - } - }, - "/vessels/{imo}": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "imo", - "in": "path", - "required": true - }, - { - "schema": { - "type": "string", - "format": "date-time", - "example": "2025-05-20T00:00:00Z" - }, - "name": "show_positions[from_timestamp]", - "in": "query", - "description": "ISO 8601 timestamp to filter positions from. 7 days by default.", - "required": false - }, - { - "schema": { - "type": "string", - "format": "date-time", - "example": "2025-05-24T00:00:00Z" - }, - "name": "show_positions[to_timestamp]", - "in": "query", - "description": "ISO 8601 timestamp to filter positions up to. Current time by default.", - "required": false - } - ], - "get": { - "summary": "Get a vessel using the imo", - "tags": [ - "Vessels" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/vessel" - } - } - } - } - } - }, - "403": { - "description": "Forbidden - Feature not enabled", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "items": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "403" - }, - "source": { - "type": "object", - "nullable": true - }, - "title": { - "type": "string", - "example": "Forbidden" - }, - "detail": { - "type": "string", - "example": "Routing data feature is not enabled for this account" - } - } - } - } - } - } - } - } - } - }, - "operationId": "get-vessels-imo", - "description": "Returns a vessel by the given IMO number. `show_positions` is a paid feature. Please contact sales@terminal49.com.", - "x-internal": true - } - }, - "/vessels/{id}/future_positions": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "get": { - "summary": "Get vessel future positions", - "tags": [ - "Routing (Paid)", - "Vessels" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/vessel_with_positions" - }, - "links": { - "type": "object", - "properties": { - "self": { - "type": "string", - "format": "uri" - } - } - } - } - } - } - } - }, - "403": { - "description": "Forbidden - Routing data feature is not enabled for this account", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "items": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "403" - }, - "title": { - "type": "string", - "example": "Forbidden" - }, - "detail": { - "type": "string", - "example": "Routing data feature is not enabled for this account" - } - } - } - } - } - } - } - } - } - }, - "operationId": "get-vessels-id-future-positions", - "description": "Returns the estimated route between two ports for a given vessel. The timestamp of the positions has fixed spacing of one minute. This is a paid feature. Please contact sales@terminal49.com.", - "parameters": [ - { - "schema": { - "type": "string", - "format": "uuid" - }, - "in": "query", - "name": "port_id", - "description": "The destination port id", - "required": true - }, - { - "schema": { - "type": "string", - "format": "uuid" - }, - "in": "query", - "name": "previous_port_id", - "description": "The previous port id", - "required": true - } - ] - } - }, - "/vessels/{id}/future_positions_with_coordinates": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "get": { - "summary": "Get vessel future positions from coordinates", - "tags": [ - "Routing (Paid)", - "Vessels" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/vessel_with_positions" - }, - "links": { - "type": "object", - "properties": { - "self": { - "type": "string", - "format": "uri" - } - } - } - } - } - } - } - }, - "403": { - "description": "Forbidden - Routing data feature is not enabled for this account", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "items": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "403" - }, - "title": { - "type": "string", - "example": "Forbidden" - }, - "detail": { - "type": "string", - "example": "Routing data feature is not enabled for this account" - } - } - } - } - } - } - } - } - } - }, - "operationId": "get-vessels-id-future-positions-with-coordinates", - "description": "Returns the estimated route between two ports for a given vessel from a set of coordinates. The timestamp of the positions has fixed spacing of one minute. This is a paid feature. Please contact sales@terminal49.com.", - "parameters": [ - { - "schema": { - "type": "string", - "format": "uuid" - }, - "in": "query", - "name": "port_id", - "description": "The destination port id", - "required": true - }, - { - "schema": { - "type": "string", - "format": "uuid" - }, - "in": "query", - "name": "previous_port_id", - "description": "The previous port id", - "required": true - }, - { - "schema": { - "type": "number" - }, - "in": "query", - "name": "latitude", - "description": "Starting latitude coordinate", - "required": true - }, - { - "schema": { - "type": "number" - }, - "in": "query", - "name": "longitude", - "description": "Starting longitude coordinate", - "required": true - } - ] - } - }, - "/terminals/{id}": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "get": { - "summary": "Get a terminal using the id", - "tags": [ - "Terminals" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/terminal" - } - } - } - } - } - } - }, - "operationId": "get-terminal-id", - "description": "Return the details of a single terminal." - } - }, - "/parties": { - "get": { - "description": "Get a list of parties", - "operationId": "list-parties", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/party" - } - }, - "links": { - "$ref": "#/components/schemas/links" - }, - "meta": { - "$ref": "#/components/schemas/meta" - } - } - } - } - } - } - }, - "parameters": [ - { - "schema": { - "type": "integer", - "default": 1 - }, - "in": "query", - "name": "page[number]" - }, - { - "schema": { - "type": "integer", - "default": 25 - }, - "in": "query", - "name": "page[size]", - "description": "" - } - ], - "tags": [ - "Parties" - ] - }, - "post": { - "description": "Creates a new party", - "operationId": "post-party", - "responses": { - "201": { - "description": "Party Created", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/party" - }, - "links": { - "$ref": "#/components/schemas/link-self" - } - } - }, - "examples": { - "New Party": { - "value": { - "data": { - "id": "ba4cb904-827f-4038-8e31-1e92b3356218", - "type": "party", - "attributes": { - "company_name": "COMPANY NAME" - } - }, - "links": { - "self": "/v2/parties/ba4cb904-827f-4038-8e31-1e92b3356218" - } - } - } - } - } - }, - "headers": {} - }, - "422": { - "description": "Unprocessable Entity", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/error" - } - } - } - }, - "examples": { - "Error Examples": { - "value": { - "errors": [ - { - "status": "422", - "source": { - "pointer": "/data/attributes/company_name" - }, - "title": "Unprocessable Entity", - "detail": "Company name can't be blank" - }, - { - "status": "422", - "source": { - "pointer": "/data/attributes/customer" - }, - "title": "Unprocessable Entity", - "detail": "'XXXX' already exists in Account" - } - ] - } - } - } - } - } - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "object", - "required": [ - "attributes" - ], - "properties": { - "attributes": { - "type": "object", - "properties": { - "company_name": { - "type": "string", - "example": "COMPANY NAME", - "description": "The name of the company" - } - } - } - } - } - } - } - } - } - }, - "tags": [ - "Parties" - ] - } - }, - "/parties/{id}": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "id", - "in": "path", - "required": true - } - ], - "get": { - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/party" - }, - "links": { - "$ref": "#/components/schemas/link-self" - } - } - } - } - } - } - }, - "operationId": "get-parties-id", - "description": "Returns a party by it's given identifier", - "tags": [ - "Parties" - ] - }, - "patch": { - "description": "Updates a party", - "operationId": "edit-party", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/party" - }, - "links": { - "$ref": "#/components/schemas/link-self" - } - } - } - } - } - }, - "422": { - "description": "Unprocessable Entity", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/error" - } - } - } - }, - "examples": { - "Error Examples": { - "value": { - "errors": [ - { - "status": "422", - "source": { - "pointer": "/data/attributes/company_name" - }, - "title": "Unprocessable Entity", - "detail": "Company name can't be blank" - }, - { - "status": "422", - "source": { - "pointer": "/data/attributes/customer" - }, - "title": "Unprocessable Entity", - "detail": "'XXXX' already exists in Account" - } - ] - } - } - } - } - } - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "object", - "required": [ - "attributes" - ], - "properties": { - "attributes": { - "type": "object", - "properties": { - "company_name": { - "type": "string", - "example": "COMPANY NAME", - "description": "The name of the company" - } - } - } - } - } - } - } - } - } - }, - "tags": [ - "Parties" - ] - } - } - }, - "x-tagGroups": [ - { - "name": "End Points", - "tags": [ - "Shipments", - "Containers", - "Tracking Requests", - "Webhooks", - "Webhook Notifications", - "Metro Areas" - ] - }, - { - "name": "Routing (Paid)", - "tags": [ - "Routing (Paid)" - ] - } - ], - "components": { - "schemas": { - "shipment": { - "title": "Shipment model", - "type": "object", - "x-examples": {}, - "description": "", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "relationships": { - "type": "object", - "properties": { - "destination": { - "type": "object", - "properties": { - "data": { - "type": "object", - "nullable": true, - "properties": { - "type": { - "type": "string", - "enum": [ - "port", - "metro_area" - ] - }, - "id": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "type", - "id" - ] - } - } - }, - "port_of_lading": { - "type": "object", - "properties": { - "data": { - "type": "object", - "nullable": true, - "properties": { - "type": { - "type": "string", - "enum": [ - "port" - ] - }, - "id": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "type", - "id" - ] - } - } - }, - "containers": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "container" - ] - }, - "id": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "type", - "id" - ] - } - } - } - }, - "port_of_discharge": { - "type": "object", - "properties": { - "data": { - "type": "object", - "nullable": true, - "properties": { - "type": { - "type": "string", - "enum": [ - "port" - ] - }, - "id": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "type", - "id" - ] - } - } - }, - "pod_terminal": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "terminal" - ] - }, - "id": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "type", - "id" - ] - } - } - }, - "destination_terminal": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "terminal", - "rail_terminal" - ] - }, - "id": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "type", - "id" - ] - } - } - }, - "line_tracking_stopped_by_user": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "user" - ] - }, - "id": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "type", - "id" - ] - } - } - } - } - }, - "attributes": { - "type": "object", - "properties": { - "bill_of_lading_number": { - "type": "string" - }, - "normalized_number": { - "type": "string", - "description": "The normalized version of the shipment number used for querying the carrier" - }, - "ref_numbers": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "port_of_lading_locode": { - "type": "string", - "description": "UN/LOCODE", - "nullable": true - }, - "port_of_lading_name": { - "type": "string", - "nullable": true - }, - "port_of_discharge_locode": { - "type": "string", - "description": "UN/LOCODE", - "nullable": true - }, - "port_of_discharge_name": { - "type": "string", - "nullable": true - }, - "destination_locode": { - "type": "string", - "description": "UN/LOCODE", - "nullable": true - }, - "destination_name": { - "type": "string", - "nullable": true - }, - "shipping_line_scac": { - "type": "string" - }, - "shipping_line_name": { - "type": "string" - }, - "shipping_line_short_name": { - "type": "string" - }, - "customer_name": { - "type": "string", - "nullable": true - }, - "pod_vessel_name": { - "type": "string", - "nullable": true - }, - "pod_vessel_imo": { - "type": "string", - "nullable": true - }, - "pod_voyage_number": { - "type": "string", - "nullable": true - }, - "pol_etd_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "pol_atd_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "pod_eta_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "pod_original_eta_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "pod_ata_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "destination_eta_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "destination_ata_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "pol_timezone": { - "type": "string", - "description": "IANA tz", - "nullable": true - }, - "pod_timezone": { - "type": "string", - "description": "IANA tz", - "nullable": true - }, - "destination_timezone": { - "type": "string", - "description": "IANA tz", - "nullable": true - }, - "line_tracking_last_attempted_at": { - "type": "string", - "format": "date-time", - "description": "When Terminal49 last tried to update the shipment status from the shipping line", - "nullable": true - }, - "line_tracking_last_succeeded_at": { - "type": "string", - "format": "date-time", - "description": "When Terminal49 last successfully updated the shipment status from the shipping line", - "nullable": true - }, - "line_tracking_stopped_at": { - "type": "string", - "format": "date-time", - "description": "When Terminal49 stopped checking at the shipping line", - "nullable": true - }, - "line_tracking_stopped_reason": { - "type": "string", - "enum": [ - "all_containers_terminated", - "past_arrival_window", - "no_updates_at_line", - "cancelled_by_user", - "booking_cancelled", - null - ], - "description": "The reason Terminal49 stopped checking", - "nullable": true - } - }, - "required": [ - "bill_of_lading_number" - ] - }, - "type": { - "type": "string", - "enum": [ - "shipment" - ] - }, - "links": { - "type": "object", - "properties": { - "self": { - "type": "string", - "format": "uri" - } - }, - "required": [ - "self" - ] - } - }, - "required": [ - "id", - "type", - "attributes", - "relationships", - "links" - ] - }, - "meta": { - "title": "meta", - "type": "object", - "properties": { - "size": { - "type": "integer" - }, - "total": { - "type": "integer" - } - } - }, - "link-self": { - "title": "link", - "type": "object", - "properties": { - "self": { - "type": "string", - "format": "uri" - } - } - }, - "links": { - "title": "links", - "type": "object", - "properties": { - "last": { - "type": "string", - "format": "uri" - }, - "next": { - "type": "string", - "format": "uri" - }, - "prev": { - "type": "string", - "format": "uri" - }, - "first": { - "type": "string", - "format": "uri" - }, - "self": { - "type": "string", - "format": "uri" - } - } - }, - "container": { - "title": "Container model", - "type": "object", - "x-examples": { - "Example Container": { - "id": "ff77a822-23a7-4ccd-95ca-g534c071baaf3", - "type": "container", - "attributes": { - "number": "KOCU4959010", - "ref_numbers": [ - "REF-1", - "REF-2" - ], - "seal_number": "210084213", - "created_at": "2021-10-18T09:52:33Z", - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "high_cube", - "weight_in_lbs": 20210, - "fees_at_pod_terminal": [], - "holds_at_pod_terminal": [], - "pickup_lfd": "2022-01-21T08:00:00Z", - "pickup_appointment_at": null, - "pod_full_out_chassis_number": "APMZ418805", - "location_at_pod_terminal": "Delivered 02/11/2022 14:18", - "availability_known": true, - "available_for_pickup": false, - "pod_arrived_at": "2022-01-03T10:30:00Z", - "pod_discharged_at": "2022-01-08T09:15:00Z", - "final_destination_full_out_at": null, - "pod_full_out_at": "2022-02-11T22:18:00Z", - "empty_terminated_at": null, - "terminal_checked_at": "2022-02-11T22:45:32Z", - "pod_rail_carrier_scac": "UPRR", - "ind_rail_carrier_scac": "CSXT", - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": null, - "empty_terminated_timezone": null, - "pod_last_tracking_request_at": "2022-02-11T22:40:00Z", - "shipment_last_tracking_request_at": "2022-02-11T22:40:00Z", - "pod_rail_loaded_at": "2022-02-11T22:18:00Z", - "pod_rail_departed_at": "2022-02-11T23:30:00Z", - "ind_eta_at": null, - "ind_ata_at": "2022-02-15T01:12:00Z", - "ind_rail_unloaded_at": "2022-02-15T07:54:00Z", - "ind_facility_lfd_on": null - }, - "relationships": { - "shipment": { - "data": { - "id": "x92acf88-c263-43ddf-b005-aca2a32d47f1", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "x551cac7-aff5-40a6-9c63-49facf19cc3df", - "type": "terminal" - } - }, - "pickup_facility": { - "data": { - "id": "d7d8d314-b02b-4caa-b04f-d3d4726f4107", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "xecfe2d1-c498-4022-a9f8-ec56722e1215", - "type": "transport_event" - }, - { - "id": "2900a9b8-d9e2-4696-abd86-4a767b885d23", - "type": "transport_event" - }, - { - "id": "5ad0dce1-x78e4-464d-af5f-a36190428a2c", - "type": "transport_event" - }, - { - "id": "876575d5-5ede-40d6-a093-c3a4cfcxaa1c7", - "type": "transport_event" - }, - { - "id": "dc2a9d8f-75e6-43xa5-a04e-58458495f08c", - "type": "transport_event" - }, - { - "id": "50xd2ea1-01ac-473d-8a08-3b5d77d2b793", - "type": "transport_event" - }, - { - "id": "9d1f55xe3-6758-4be7-872a-30451ddd957e", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - { - "id": "38084a1d-a2eb-434e-81ac3-606c89a61c4b", - "type": "raw_event" - }, - { - "id": "53680df3-93d5-4385-86c5-a33ee41b4c1f", - "type": "raw_event" - }, - { - "id": "7d9cdf70-51e8-4b75-a8229-f5d691495ab6", - "type": "raw_event" - }, - { - "id": "e62d41ac-8738-42e8-b582-35ef28ae88e2", - "type": "raw_event" - }, - { - "id": "1209172b-acd8-4ce0-8821-dbc4934208b3", - "type": "raw_event" - }, - { - "id": "4265ea5f-2b9a-436f-98fa-803d8ed49acb2", - "type": "raw_event" - }, - { - "id": "c3cb2eb7-6c0a-4db8-8742-517b97b175d5", - "type": "raw_event" - }, - { - "id": "b1959f36-a218-4b6e-863a9-2e0b4ad5159c", - "type": "raw_event" - } - ] - } - } - } - }, - "description": "Represents the equipment during a specific journey.", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "container" - ] - }, - "attributes": { - "type": "object", - "properties": { - "number": { - "type": "string" - }, - "ref_numbers": { - "type": "array", - "items": { - "type": "string" - } - }, - "equipment_type": { - "type": "string", - "enum": [ - "dry", - "reefer", - "open top", - "flat rack", - "bulk", - "tank", - null - ], - "nullable": true - }, - "equipment_length": { - "type": "integer", - "enum": [ - null, - 10, - 20, - 40, - 45 - ], - "nullable": true - }, - "equipment_height": { - "type": "string", - "enum": [ - "standard", - "high_cube", - null - ], - "nullable": true - }, - "weight_in_lbs": { - "type": "number", - "nullable": true - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "seal_number": { - "type": "string", - "nullable": true - }, - "pickup_lfd": { - "type": "string", - "format": "date-time", - "description": "The last free day for pickup before demmurage accrues. Corresponding timezone is pod_timezone.", - "nullable": true - }, - "pickup_appointment_at": { - "type": "string", - "format": "date-time", - "description": "When available the pickup appointment time at the terminal is returned.", - "nullable": true - }, - "availability_known": { - "type": "boolean", - "description": "Whether Terminal 49 is receiving availability status from the terminal." - }, - "available_for_pickup": { - "type": "boolean", - "description": "If availability_known is true, then whether container is available to be picked up at terminal.", - "nullable": true - }, - "pod_arrived_at": { - "type": "string", - "format": "date-time", - "description": "Time the vessel arrived at the POD", - "nullable": true - }, - "pod_discharged_at": { - "type": "string", - "format": "date-time", - "description": "Discharge time at the port of discharge", - "nullable": true - }, - "pod_full_out_at": { - "type": "string", - "format": "date-time", - "description": "Full Out time at port of discharge. Null for inland moves.", - "nullable": true - }, - "terminal_checked_at": { - "type": "string", - "format": "date-time", - "description": "When the terminal was last checked.", - "nullable": true - }, - "pod_full_out_chassis_number": { - "type": "string", - "description": "The chassis number used when container was picked up at POD (if available)", - "nullable": true - }, - "location_at_pod_terminal": { - "type": "string", - "description": "Location at port of discharge terminal", - "nullable": true - }, - "final_destination_full_out_at": { - "type": "string", - "format": "date-time", - "description": "Pickup time at final destination for inland moves.", - "nullable": true - }, - "empty_terminated_at": { - "type": "string", - "format": "date-time", - "description": "Time empty container was returned.", - "nullable": true - }, - "holds_at_pod_terminal": { - "type": "array", - "items": { - "$ref": "#/components/schemas/terminal_hold" - } - }, - "fees_at_pod_terminal": { - "type": "array", - "items": { - "$ref": "#/components/schemas/terminal_fee" - } - }, - "pod_timezone": { - "type": "string", - "description": "IANA tz. Applies to attributes pod_arrived_at, pod_discharged_at, pickup_appointment_at, pod_full_out_at.", - "nullable": true - }, - "final_destination_timezone": { - "type": "string", - "description": "IANA tz. Applies to attribute final_destination_full_out_at.", - "nullable": true - }, - "empty_terminated_timezone": { - "type": "string", - "description": "IANA tz. Applies to attribute empty_terminated_at.", - "nullable": true - }, - "pod_rail_carrier_scac": { - "type": "string", - "description": "The SCAC of the rail carrier for the pickup leg of the container's journey.(BETA)", - "nullable": true - }, - "ind_rail_carrier_scac": { - "type": "string", - "description": "The SCAC of the rail carrier for the delivery leg of the container's journey.(BETA)", - "nullable": true - }, - "pod_last_tracking_request_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "shipment_last_tracking_request_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "pod_rail_loaded_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "pod_rail_departed_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "ind_eta_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "ind_ata_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "ind_rail_unloaded_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "ind_facility_lfd_on": { - "type": "string", - "format": "date-time", - "nullable": true - } - } - }, - "relationships": { - "type": "object", - "properties": { - "shipment": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "shipment" - ] - } - } - } - } - }, - "pickup_facility": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "terminal" - ] - } - } - } - } - }, - "pod_terminal": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "terminal" - ] - } - } - } - } - }, - "transport_events": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "transport_event" - ] - } - } - } - } - } - }, - "raw_events": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "raw_event" - ] - } - } - } - } - } - } - } - } - }, - "required": [ - "id", - "type", - "attributes" - ] - }, - "port": { - "title": "Port model", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "attributes": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "code": { - "type": "string", - "description": "UN/LOCODE" - }, - "state_abbr": { - "type": "string", - "x-stoplight": { - "id": "jixah1a0q3exs" - }, - "nullable": true - }, - "city": { - "type": "string", - "x-stoplight": { - "id": "657ij4boc7kyv" - }, - "nullable": true - }, - "country_code": { - "type": "string", - "description": "2 digit country code" - }, - "time_zone": { - "type": "string", - "description": "IANA tz" - }, - "latitude": { - "type": "number", - "x-stoplight": { - "id": "480os7a90z6kk" - }, - "nullable": true - }, - "longitude": { - "type": "number", - "x-stoplight": { - "id": "nfdetqgx5p1yv" - }, - "nullable": true - } - } - }, - "type": { - "type": "string", - "enum": [ - "port" - ] - } - }, - "required": [ - "id", - "type" - ] - }, - "shipping_line": { - "title": "Shipping line model", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "attributes": { - "type": "object", - "required": [ - "scac", - "name", - "alternative_scacs", - "short_name", - "bill_of_lading_tracking_support", - "booking_number_tracking_support", - "container_number_tracking_support" - ], - "properties": { - "scac": { - "type": "string", - "minLength": 4, - "maxLength": 4 - }, - "name": { - "type": "string" - }, - "alternative_scacs": { - "type": "array", - "x-stoplight": { - "id": "jwf70hnip0xwb" - }, - "description": "Additional SCACs which will be accepted in tracking requests", - "items": { - "x-stoplight": { - "id": "nrqnwg5y2u0ni" - }, - "type": "string", - "minLength": 4, - "maxLength": 4 - } - }, - "short_name": { - "type": "string" - }, - "bill_of_lading_tracking_support": { - "type": "boolean" - }, - "booking_number_tracking_support": { - "type": "boolean" - }, - "container_number_tracking_support": { - "type": "boolean" - } - } - }, - "type": { - "type": "string", - "enum": [ - "shipping_line" - ] - } - }, - "required": [ - "id", - "attributes", - "type" - ] - }, - "account": { - "title": "Account model", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "container" - ] - }, - "attributes": { - "type": "object", - "required": [ - "company_name" - ], - "properties": { - "company_name": { - "type": "string" - } - } - } - }, - "required": [ - "id", - "type", - "attributes" - ], - "x-examples": {} - }, - "error": { - "title": "Error model", - "type": "object", - "properties": { - "detail": { - "type": "string", - "nullable": true - }, - "title": { - "type": "string", - "nullable": true - }, - "source": { - "type": "object", - "nullable": true, - "properties": { - "pointer": { - "type": "string", - "nullable": true - }, - "parameter": { - "type": "string", - "nullable": true - } - } - }, - "code": { - "type": "string", - "nullable": true - }, - "status": { - "type": "string", - "nullable": true - }, - "meta": { - "type": "object", - "nullable": true, - "properties": { - "tracking_request_id": { - "type": "string", - "format": "uuid", - "nullable": true - } - } - } - }, - "required": [ - "title" - ] - }, - "metro_area": { - "title": "Metro area model", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "attributes": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "code": { - "type": "string", - "description": "UN/LOCODE" - }, - "state_abbr": { - "type": "string", - "x-stoplight": { - "id": "j9yuwej2ym7yq" - }, - "nullable": true - }, - "country_code": { - "type": "string", - "x-stoplight": { - "id": "hfupdk750wcrj" - } - }, - "time_zone": { - "type": "string", - "description": "IANA tz", - "x-stoplight": { - "id": "izvtty345nfsz" - } - }, - "latitude": { - "type": "number", - "x-stoplight": { - "id": "9l62t4cwsp53w" - }, - "nullable": true - }, - "longitude": { - "type": "number", - "x-stoplight": { - "id": "3tzibc0li8xvg" - }, - "nullable": true - } - } - }, - "type": { - "type": "string", - "enum": [ - "metro_area" - ] - }, - "": { - "type": "string", - "x-stoplight": { - "id": "kwcjunrtu3r5o" - } - } - }, - "required": [ - "id", - "type" - ] - }, - "terminal": { - "title": "Terminal model", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "relationships": { - "type": "object", - "required": [ - "port" - ], - "properties": { - "port": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "port" - ] - } - } - } - } - } - } - }, - "attributes": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string" - }, - "nickname": { - "type": "string" - }, - "firms_code": { - "type": "string", - "description": "CBP FIRMS Code or CBS Sublocation Code" - }, - "smdg_code": { - "type": "string", - "description": "SMDG Code" - }, - "bic_facility_code": { - "type": "string", - "description": "BIC Facility Code" - }, - "street": { - "type": "string", - "description": "Street part of the address" - }, - "city": { - "type": "string", - "description": "City part of the address" - }, - "state": { - "type": "string", - "description": "State part of the address" - }, - "state_abbr": { - "type": "string", - "description": "State abbreviation for the state" - }, - "zip": { - "type": "string", - "description": "ZIP code part of the address" - }, - "country": { - "type": "string", - "description": "Country part of the address" - } - } - }, - "type": { - "type": "string", - "enum": [ - "terminal" - ] - } - }, - "required": [ - "attributes", - "relationships" - ] - }, - "rail_terminal": { - "title": "Rail Terminal model", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "relationships": { - "type": "object", - "properties": { - "port": { - "type": "object", - "properties": { - "data": { - "type": "object", - "nullable": true, - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "port" - ] - } - } - } - } - }, - "metro_area": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "metro_area" - ] - } - } - } - } - } - } - }, - "attributes": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string" - }, - "nickname": { - "type": "string" - }, - "firms_code": { - "type": "string", - "description": "CBP FIRMS Code or CBS Sublocation Code" - } - } - }, - "type": { - "type": "string", - "enum": [ - "rail_terminal" - ] - } - }, - "required": [ - "attributes" - ] - }, - "tracking_request": { - "title": "Tracking Request", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "tracking_request" - ] - }, - "attributes": { - "type": "object", - "properties": { - "request_number": { - "type": "string", - "example": "ONEYSH9AME650500" - }, - "ref_numbers": { - "type": "array", - "nullable": true, - "items": { - "type": "string" - } - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "status": { - "type": "string", - "enum": [ - "pending", - "awaiting_manifest", - "created", - "failed", - "tracking_stopped" - ] - }, - "failed_reason": { - "type": "string", - "enum": [ - "booking_cancelled", - "duplicate", - "expired", - "internal_processing_error", - "invalid_number", - "not_found", - "retries_exhausted", - "shipping_line_unreachable", - "unrecognized_response", - "data_unavailable", - null - ], - "description": "If the tracking request has failed, or is currently failing, the last reason we were unable to complete the request", - "nullable": true - }, - "request_type": { - "type": "string", - "enum": [ - "bill_of_lading", - "booking_number", - "container" - ], - "example": "bill_of_lading" - }, - "scac": { - "type": "string", - "example": "ONEY", - "minLength": 4, - "maxLength": 4 - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "updated_at": { - "type": "string", - "format": "date-time" - }, - "is_retrying": { - "type": "boolean" - }, - "retry_count": { - "type": "integer", - "description": "How many times T49 has attempted to get the shipment from the shipping line", - "nullable": true - } - }, - "required": [ - "request_number", - "status", - "request_type", - "scac", - "created_at" - ] - }, - "relationships": { - "type": "object", - "properties": { - "tracked_object": { - "type": "object", - "properties": { - "data": { - "type": "object", - "nullable": true, - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "shipment" - ] - } - } - } - } - }, - "customer": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "party" - ] - } - } - } - } - } - } - } - }, - "required": [ - "id", - "type" - ] - }, - "webhook": { - "title": "webhook", - "type": "object", - "x-examples": {}, - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "webhook" - ] - }, - "attributes": { - "type": "object", - "properties": { - "url": { - "type": "string", - "format": "uri", - "description": "https end point" - }, - "active": { - "type": "boolean", - "default": true, - "description": "Whether the webhook will be delivered when events are triggered" - }, - "events": { - "type": "array", - "description": "The list of events to enabled for this endpoint", - "uniqueItems": true, - "minItems": 1, - "items": { - "type": "string", - "enum": [ - "container.transport.vessel_arrived", - "container.transport.vessel_discharged", - "container.transport.vessel_loaded", - "container.transport.vessel_departed", - "container.transport.rail_departed", - "container.transport.rail_arrived", - "container.transport.rail_loaded", - "container.transport.rail_unloaded", - "container.transport.transshipment_arrived", - "container.transport.transshipment_discharged", - "container.transport.transshipment_loaded", - "container.transport.transshipment_departed", - "container.transport.feeder_arrived", - "container.transport.feeder_discharged", - "container.transport.feeder_loaded", - "container.transport.feeder_departed", - "container.transport.empty_out", - "container.transport.full_in", - "container.transport.full_out", - "container.transport.empty_in", - "container.transport.vessel_berthed", - "shipment.estimated.arrival", - "tracking_request.succeeded", - "tracking_request.failed", - "tracking_request.awaiting_manifest", - "tracking_request.tracking_stopped", - "container.created", - "container.updated", - "container.pod_terminal_changed", - "container.transport.arrived_at_inland_destination", - "container.transport.estimated.arrived_at_inland_destination", - "container.pickup_lfd.changed", - "container.transport.available" - ] - } - }, - "secret": { - "type": "string", - "description": "A random token that will sign all delivered webhooks" - }, - "headers": { - "type": "array", - "nullable": true, - "items": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - } - } - }, - "required": [ - "url", - "active", - "events", - "secret" - ] - } - }, - "required": [ - "id", - "type" - ], - "description": "" - }, - "vessel": { - "title": "vessel", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "vessel" - ] - }, - "attributes": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the ship or vessel", - "example": "Ever Given" - }, - "imo": { - "type": "string", - "description": "International Maritime Organization (IMO) number", - "nullable": true, - "example": "9811000" - }, - "mmsi": { - "type": "string", - "description": "Maritime Mobile Service Identity (MMSI)", - "nullable": true, - "example": "353136000" - }, - "latitude": { - "type": "number", - "description": "The current latitude position of the vessel", - "nullable": true, - "example": 25.29845 - }, - "longitude": { - "type": "number", - "description": "The current longitude position of the vessel", - "nullable": true, - "example": 121.217 - }, - "nautical_speed_knots": { - "type": "number", - "description": "The current speed of the ship in knots (nautical miles per hour)", - "nullable": true, - "example": 90 - }, - "navigational_heading_degrees": { - "type": "number", - "description": "The current heading of the ship in degrees, where 0 is North, 90 is East, 180 is South, and 270 is West", - "nullable": true, - "example": 194 - }, - "position_timestamp": { - "type": "string", - "description": "The timestamp of when the ship's position was last recorded, in ISO 8601 date and time format", - "nullable": true, - "example": "2023-07-28T14:01:37Z" - }, - "positions": { - "type": "array", - "description": "An array of historical position data for the vessel. Only included if `show_positions` is true.", - "nullable": true, - "items": { - "type": "object", - "properties": { - "latitude": { - "type": "number", - "example": 1.477285 - }, - "longitude": { - "type": "number", - "example": 104.535533333 - }, - "heading": { - "type": "number", - "nullable": true, - "example": 51 - }, - "timestamp": { - "type": "string", - "format": "date-time", - "example": "2025-05-23T19:14:22Z" - }, - "estimated": { - "type": "boolean", - "example": false - } - } - } - } - } - } - } - }, - "transport_event": { - "title": "Transport Event Model", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "transport_event" - ] - }, - "attributes": { - "type": "object", - "properties": { - "event": { - "type": "string", - "enum": [ - "container.transport.vessel_arrived", - "container.transport.vessel_discharged", - "container.transport.vessel_loaded", - "container.transport.vessel_departed", - "container.transport.rail_departed", - "container.transport.rail_arrived", - "container.transport.rail_loaded", - "container.transport.rail_unloaded", - "container.transport.transshipment_arrived", - "container.transport.transshipment_discharged", - "container.transport.transshipment_loaded", - "container.transport.transshipment_departed", - "container.transport.feeder_arrived", - "container.transport.feeder_discharged", - "container.transport.feeder_loaded", - "container.transport.feeder_departed", - "container.transport.empty_out", - "container.transport.full_in", - "container.transport.full_out", - "container.transport.empty_in", - "container.transport.vessel_berthed", - "container.transport.arrived_at_inland_destination", - "container.transport.estimated.arrived_at_inland_destination", - "container.pickup_lfd.changed", - "container.transport.available" - ] - }, - "voyage_number": { - "type": "string", - "nullable": true - }, - "timestamp": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "timezone": { - "type": "string", - "description": "IANA tz", - "nullable": true - }, - "location_locode": { - "type": "string", - "description": "UNLOCODE of the event location", - "nullable": true - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "data_source": { - "type": "string", - "enum": [ - "shipping_line", - "terminal", - "ais" - ], - "example": "shipping_line", - "description": "The original source of the event data" - } - } - }, - "relationships": { - "type": "object", - "properties": { - "shipment": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "shipment" - ] - } - } - } - } - }, - "location": { - "type": "object", - "properties": { - "data": { - "type": "object", - "nullable": true, - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "port", - "metro_area" - ] - } - } - } - } - }, - "vessel": { - "type": "object", - "properties": { - "data": { - "type": "object", - "nullable": true, - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "name": { - "type": "string", - "enum": [ - "vessel" - ] - } - } - } - } - }, - "terminal": { - "type": "object", - "properties": { - "data": { - "type": "object", - "nullable": true, - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "terminal", - "rail_terminal" - ] - } - } - } - } - }, - "container": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "container" - ] - } - } - } - } - } - } - } - }, - "required": [ - "id", - "type" - ] - }, - "estimated_event": { - "title": "Estimated Event Model", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "estimated_event" - ] - }, - "attributes": { - "type": "object", - "required": [ - "created_at", - "estimated_timestamp", - "event" - ], - "properties": { - "created_at": { - "type": "string", - "description": "When the estimated event was created", - "format": "date-time" - }, - "estimated_timestamp": { - "type": "string", - "format": "date-time" - }, - "event": { - "type": "string", - "enum": [ - "shipment.estimated.arrival" - ] - }, - "location_locode": { - "type": "string", - "description": "UNLOCODE of the event location", - "nullable": true - }, - "timezone": { - "type": "string", - "description": "IANA tz", - "nullable": true - }, - "voyage_number": { - "type": "string", - "nullable": true - }, - "data_source": { - "type": "string", - "enum": [ - "shipping_line", - "terminal" - ], - "description": "The original source of the event data" - } - } - }, - "relationships": { - "type": "object", - "required": [ - "shipment" - ], - "properties": { - "shipment": { - "type": "object", - "required": [ - "data" - ], - "properties": { - "data": { - "type": "object", - "required": [ - "id", - "type" - ], - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "shipment" - ] - } - } - } - } - }, - "port": { - "type": "object", - "properties": { - "data": { - "type": "object", - "nullable": true, - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "port" - ] - } - } - } - } - }, - "vessel": { - "type": "object", - "description": "\n", - "properties": { - "data": { - "type": "object", - "nullable": true, - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "vessel" - ] - } - } - } - } - } - } - } - }, - "required": [ - "id", - "type", - "attributes", - "relationships" - ] - }, - "webhook_notification": { - "title": "webhook_notification", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "webhook_notification" - ] - }, - "attributes": { - "type": "object", - "properties": { - "event": { - "type": "string", - "enum": [ - "container.transport.vessel_arrived", - "container.transport.vessel_discharged", - "container.transport.vessel_loaded", - "container.transport.vessel_departed", - "container.transport.rail_departed", - "container.transport.rail_arrived", - "container.transport.rail_loaded", - "container.transport.rail_unloaded", - "container.transport.transshipment_arrived", - "container.transport.transshipment_discharged", - "container.transport.transshipment_loaded", - "container.transport.transshipment_departed", - "container.transport.feeder_arrived", - "container.transport.feeder_discharged", - "container.transport.feeder_loaded", - "container.transport.feeder_departed", - "container.transport.empty_out", - "container.transport.full_in", - "container.transport.full_out", - "container.transport.empty_in", - "container.transport.vessel_berthed", - "shipment.estimated.arrival", - "tracking_request.succeeded", - "tracking_request.failed", - "tracking_request.awaiting_manifest", - "tracking_request.tracking_stopped", - "container.created", - "container.updated", - "container.pod_terminal_changed", - "container.transport.arrived_at_inland_destination", - "container.transport.estimated.arrived_at_inland_destination", - "container.pickup_lfd.changed", - "container.transport.available" - ] - }, - "delivery_status": { - "type": "string", - "default": "pending", - "enum": [ - "pending", - "succeeded", - "failed" - ], - "description": "Whether the notification has been delivered to the webhook endpoint" - }, - "created_at": { - "type": "string" - } - }, - "required": [ - "event", - "delivery_status", - "created_at" - ] - }, - "relationships": { - "type": "object", - "properties": { - "webhook": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "webhook" - ] - } - } - } - } - }, - "reference_object": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "tracking_request", - "estimated_event", - "transport_event", - "container_updated_event" - ] - } - } - } - } - } - }, - "required": [ - "webhook" - ] - } - } - }, - "terminal_hold": { - "title": "terminal_hold", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "pending", - "hold" - ] - }, - "description": { - "type": "string", - "description": "Text description from the terminal (if any)", - "nullable": true - } - }, - "required": [ - "name", - "status" - ] - }, - "terminal_fee": { - "title": "terminal_fee", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "demurrage", - "exam", - "extended_dwell_time", - "other", - "total" - ] - }, - "amount": { - "type": "number", - "description": "The fee amount in local currency" - }, - "currency_code": { - "type": "string", - "description": "The ISO 4217 currency code of the fee is charged in. E.g. USD", - "example": "USD" - } - }, - "required": [ - "type", - "amount" - ] - }, - "container_updated_event": { - "title": "container_updated_event", - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string" - }, - "attributes": { - "type": "object", - "properties": { - "changeset": { - "type": "object", - "description": "A hash of all the changed attributes with the values being an array of the before and after. E.g. \n`{\"pickup_lfd\": [null, \"2020-05-20\"]}`\n\nThe current attributes that can be alerted on are:\n- `available_for_pickup`\n- `pickup_lfd`\n- `fees_at_pod_terminal`\n- `holds_at_pod_terminal`\n- `pickup_appointment_at`\n- `pod_terminal`" - }, - "timestamp": { - "type": "string", - "format": "date-time", - "description": "" - }, - "timezone": { - "type": "string", - "description": "IANA tz " - }, - "data_source": { - "type": "string", - "enum": [ - "terminal" - ], - "example": "terminal" - } - }, - "required": [ - "changeset", - "timestamp" - ] - }, - "relationships": { - "type": "object", - "required": [ - "container", - "terminal" - ], - "properties": { - "container": { - "type": "object", - "required": [ - "data" - ], - "properties": { - "data": { - "type": "object", - "required": [ - "id", - "type" - ], - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "container" - ] - } - } - } - } - }, - "terminal": { - "type": "object", - "description": "", - "required": [ - "data" - ], - "properties": { - "data": { - "type": "object", - "required": [ - "id", - "type" - ], - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "terminal" - ] - } - } - } - } - } - } - } - }, - "required": [ - "relationships" - ] - }, - "raw_event": { - "title": "Raw Event Model", - "type": "object", - "description": "Raw Events represent the milestones from the shipping line for a given container.\n\n### About raw_event datetimes\n\nThe events may include estimated future events. The event is a future event if an `estimated_` timestamp is not null. \n\nThe datetime properties `timestamp` and `estimated`. \n\nWhen the `time_zone` property is present the datetimes are UTC timestamps, which can be converted to the local time by parsing the provided `time_zone`.\n\nWhen the `time_zone` property is absent, the datetimes represent local times which serialized as UTC timestamps for consistency. ", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "description": "", - "enum": [ - "raw_event" - ] - }, - "attributes": { - "type": "object", - "properties": { - "event": { - "type": "string", - "enum": [ - "empty_out", - "full_in", - "positioned_in", - "positioned_out", - "vessel_loaded", - "vessel_departed", - "transshipment_arrived", - "transshipment_discharged", - "transshipment_loaded", - "transshipment_departed", - "feeder_arrived", - "feeder_discharged", - "feeder_loaded", - "feeder_departed", - "rail_loaded", - "rail_departed", - "rail_arrived", - "rail_unloaded", - "vessel_arrived", - "vessel_discharged", - "arrived_at_destination", - "delivered", - "full_out", - "empty_in", - "vgm_received", - "carrier_release", - "customs_release", - "available" - ], - "description": "Normalized string representing the event", - "nullable": true - }, - "original_event": { - "type": "string", - "description": "The event name as returned by the carrier" - }, - "timestamp": { - "type": "string", - "format": "date-time", - "description": "The datetime the event either transpired or will occur in UTC" - }, - "estimated": { - "type": "boolean", - "description": "True if the timestamp is estimated, false otherwise" - }, - "actual_on": { - "type": "string", - "format": "date", - "description": "Deprecated: The date of the event at the event location when no time information is available. ", - "nullable": true - }, - "estimated_on": { - "type": "string", - "format": "date", - "description": "Deprecated: The estimated date of the event at the event location when no time information is available. ", - "nullable": true - }, - "actual_at": { - "type": "string", - "format": "date-time", - "description": "Deprecated: The datetime the event transpired in UTC", - "nullable": true - }, - "estimated_at": { - "type": "string", - "format": "date-time", - "description": "Deprecated: The estimated datetime the event will occur in UTC", - "nullable": true - }, - "timezone": { - "type": "string", - "description": "IANA tz where the event occured", - "nullable": true - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "When the raw_event was created in UTC" - }, - "location_name": { - "type": "string", - "description": "The city or facility name of the event location" - }, - "location_locode": { - "type": "string", - "description": "UNLOCODE of the event location", - "nullable": true - }, - "vessel_name": { - "type": "string", - "description": "The name of the vessel where applicable", - "nullable": true - }, - "vessel_imo": { - "type": "string", - "description": "The IMO of the vessel where applicable", - "nullable": true - }, - "index": { - "type": "integer", - "description": "The order of the event. This may be helpful when only dates (i.e. actual_on) are available." - }, - "voyage_number": { - "type": "string", - "nullable": true - } - } - }, - "relationships": { - "type": "object", - "properties": { - "location": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "port", - "metro_area" - ] - } - } - } - } - }, - "vessel": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "vessel" - ] - } - } - } - } - } - } - } - } - }, - "container_pod_terminal_changed_event": { - "title": "Container Pod Terminal Changed Event", - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string" - }, - "attributes": { - "type": "object", - "properties": { - "data_source": { - "type": "string", - "enum": [ - "shipping_line", - "terminal", - "pierpass" - ], - "example": "shipping_line", - "description": "Where the information about the terminal change came from" - }, - "timestamp": { - "type": "string", - "format": "date-time", - "description": "When the terminal change was recorded" - } - } - }, - "relationships": { - "type": "object", - "properties": { - "container": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "container" - ] - } - } - }, - "shipment": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "shipment" - ] - } - } - }, - "terminal": { - "type": "object", - "description": "The terminal the container has changed to. If this container is still on the vessel this represents an advisory. If it was previously at the terminal this represents an off-dock move.", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "terminal" - ] - } - } - } - } - } - }, - "description": "" - }, - "party": { - "title": "Party model", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "attributes": { - "type": "object", - "required": [ - "company_name" - ], - "properties": { - "company_name": { - "type": "string", - "description": "Company name" - } - } - }, - "type": { - "type": "string", - "enum": [ - "party" - ] - } - }, - "required": [ - "attributes" - ] - }, - "route": { - "title": "Route model", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "route" - ] - }, - "attributes": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "updated_at": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "id", - "created_at", - "updated_at" - ] - }, - "relationships": { - "type": "object", - "properties": { - "cargo": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "container" - ] - } - }, - "required": [ - "id", - "type" - ] - } - } - }, - "shipment": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "shipment" - ] - } - }, - "required": [ - "id", - "type" - ] - } - } - }, - "route_locations": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "route_location" - ] - } - }, - "required": [ - "id", - "type" - ] - } - } - } - } - } - } - }, - "required": [ - "id", - "type", - "attributes", - "relationships" - ] - }, - "route_location": { - "title": "Route Location model", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "route_location" - ] - }, - "attributes": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "inbound_scac": { - "type": "string", - "nullable": true, - "minLength": 4, - "maxLength": 4 - }, - "inbound_mode": { - "type": "string", - "nullable": true, - "enum": [ - "vessel", - "rail", - null - ] - }, - "inbound_eta_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "inbound_ata_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "inbound_voyage_number": { - "type": "string", - "nullable": true - }, - "outbound_scac": { - "type": "string", - "nullable": true, - "minLength": 4, - "maxLength": 4 - }, - "outbound_mode": { - "type": "string", - "nullable": true, - "enum": [ - "vessel", - "rail", - null - ] - }, - "outbound_etd_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "outbound_atd_at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "outbound_voyage_number": { - "type": "string", - "nullable": true - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "updated_at": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "id", - "created_at", - "updated_at" - ] - }, - "relationships": { - "type": "object", - "properties": { - "route": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "route" - ] - } - }, - "required": [ - "id", - "type" - ] - } - } - }, - "inbound_vessel": { - "type": "object", - "properties": { - "data": { - "type": "object", - "nullable": true, - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "vessel" - ] - } - } - } - } - }, - "outbound_vessel": { - "type": "object", - "properties": { - "data": { - "type": "object", - "nullable": true, - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "vessel" - ] - } - } - } - } - }, - "location": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "port", - "terminal" - ] - } - } - } - } - }, - "facility": { - "type": "object", - "properties": { - "data": { - "type": "object", - "nullable": true, - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "terminal", - "port" - ] - } - } - } - } - } - } - } - }, - "required": [ - "id", - "type", - "attributes", - "relationships" - ] - }, - "vessel_with_positions": { - "title": "Vessel with positions model", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "vessel" - ] - }, - "attributes": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the ship or vessel" - }, - "imo": { - "type": "string", - "description": "International Maritime Organization (IMO) number", - "nullable": true - }, - "mmsi": { - "type": "string", - "description": "Maritime Mobile Service Identity (MMSI)", - "nullable": true - }, - "latitude": { - "type": "number", - "description": "The current latitude position of the vessel", - "nullable": true - }, - "longitude": { - "type": "number", - "description": "The current longitude position of the vessel", - "nullable": true - }, - "nautical_speed_knots": { - "type": "number", - "description": "The current speed of the ship in knots (nautical miles per hour)", - "nullable": true - }, - "navigational_heading_degrees": { - "type": "number", - "description": "The current heading of the ship in degrees, where 0 is North, 90 is East, 180 is South, and 270 is West", - "nullable": true - }, - "position_timestamp": { - "type": "string", - "format": "date-time", - "description": "The timestamp of when the ship's position was last recorded, in ISO 8601 date and time format", - "nullable": true - }, - "positions": { - "type": "array", - "description": "Array of estimated future positions", - "items": { - "type": "object", - "properties": { - "latitude": { - "type": "number" - }, - "longitude": { - "type": "number" - }, - "heading": { - "type": "number", - "nullable": true - }, - "timestamp": { - "type": "string", - "format": "date-time" - }, - "estimated": { - "type": "boolean" - } - }, - "required": [ - "latitude", - "longitude", - "timestamp", - "estimated" - ] - } - } - } - } - }, - "required": [ - "id", - "type", - "attributes" - ] - } - }, - "securitySchemes": { - "authorization": { - "name": "Authorization", - "type": "apiKey", - "in": "header", - "description": "`Token YOUR_API_TOKEN`\n\nThe APIs require authentication to be done using header-based API Key and Secret Authentication. \n\nAPI key and secret are sent va the `Authorization` request header.\n\nYou send your API key and secret in the following way:\n\n`Authorization: Token YOUR_API_KEY`" - } - } - }, - "tags": [ - { - "name": "Containers" - }, - { - "name": "Shipments" - }, - { - "name": "Locations" - }, - { - "name": "Events" - }, - { - "name": "Tracking Requests" - }, - { - "name": "Webhooks" - }, - { - "name": "Webhook Notifications" - }, - { - "name": "Ports" - }, - { - "name": "Metro Areas" - }, - { - "name": "Terminals" - }, - { - "name": "Routing (Paid)" - } - ], - "security": [ - { - "authorization": [] - } - ] -} diff --git a/docs/script.js b/docs/script.js deleted file mode 100644 index 52270c1f..00000000 --- a/docs/script.js +++ /dev/null @@ -1,4 +0,0 @@ - !function(){var i="analytics",analytics=window[i]=window[i]||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","screen","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware","register"];analytics.factory=function(e){return function(){if(window[i].initialized)return window[i][e].apply(window[i],arguments);var n=Array.prototype.slice.call(arguments);if(["track","screen","alias","group","page","identify"].indexOf(e)>-1){var c=document.querySelector("link[rel='canonical']");n.push({__t:"bpc",c:c&&c.getAttribute("href")||void 0,p:location.pathname,u:location.href,s:location.search,t:document.title,r:document.referrer})}n.unshift(e);analytics.push(n);return analytics}};for(var n=0;n/src/index.ts', + '^terminal49/(.*)$': '/src/$1', + }, + modulePathIgnorePatterns: [ + '/ecosystem-tests/', + '/dist/', + '/deno/', + '/deno_tests/', + '/packages/', + ], + testPathIgnorePatterns: ['scripts'], +}; + +export default config; diff --git a/nginx.conf b/nginx.conf deleted file mode 100644 index 8686795c..00000000 --- a/nginx.conf +++ /dev/null @@ -1,54 +0,0 @@ -worker_processes 1; - -events { - worker_connections 1024; -} - -http { - include mime.types; - default_type application/octet-stream; - - sendfile on; - - keepalive_timeout 65; - - server { - listen 80; - server_name localhost; - index index.html index.htm; - - location /api/docs/ { - alias /usr/share/nginx/html/; - - if ($request_method = 'OPTIONS') { - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; - # - # Custom headers and headers various browsers *should* be OK with but aren't - # - add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; - # - # Tell client that this pre-flight info is valid for 20 days - # - add_header 'Access-Control-Max-Age' 1728000; - add_header 'Content-Type' 'text/plain charset=UTF-8'; - add_header 'Content-Length' 0; - return 204; - } - if ($request_method = 'POST') { - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; - add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; - } - if ($request_method = 'GET') { - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; - add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; - } - } - - location /api/docs { - rewrite ^ https://www.terminal49.com/api/docs/ permanent; - } - } -} diff --git a/package.json b/package.json new file mode 100644 index 00000000..368d145d --- /dev/null +++ b/package.json @@ -0,0 +1,69 @@ +{ + "name": "terminal49", + "version": "0.1.0-alpha.1", + "description": "The official TypeScript library for the Terminal49 API", + "author": "Terminal49 ", + "types": "dist/index.d.ts", + "main": "dist/index.js", + "type": "commonjs", + "repository": "github:Terminal49/API", + "license": "Apache-2.0", + "packageManager": "yarn@1.22.22", + "files": [ + "**/*" + ], + "private": false, + "publishConfig": { + "access": "public" + }, + "scripts": { + "test": "./scripts/test", + "build": "./scripts/build", + "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", + "format": "./scripts/format", + "prepare": "if ./scripts/utils/check-is-in-git-install.sh; then ./scripts/build && ./scripts/utils/git-swap.sh; fi", + "tsn": "ts-node -r tsconfig-paths/register", + "lint": "./scripts/lint", + "fix": "./scripts/format" + }, + "dependencies": {}, + "devDependencies": { + "@arethetypeswrong/cli": "^0.17.0", + "@swc/core": "^1.3.102", + "@swc/jest": "^0.2.29", + "@types/jest": "^29.4.0", + "@types/node": "^20.17.6", + "@typescript-eslint/eslint-plugin": "8.31.1", + "@typescript-eslint/parser": "8.31.1", + "eslint": "^9.20.1", + "eslint-plugin-prettier": "^5.4.1", + "eslint-plugin-unused-imports": "^4.1.4", + "iconv-lite": "^0.6.3", + "jest": "^29.4.0", + "prettier": "^3.0.0", + "publint": "^0.2.12", + "ts-jest": "^29.1.0", + "ts-node": "^10.5.0", + "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz", + "tsconfig-paths": "^4.0.0", + "tslib": "^2.8.1", + "typescript": "5.8.3", + "typescript-eslint": "8.31.1" + }, + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./*.mjs": { + "default": "./dist/*.mjs" + }, + "./*.js": { + "default": "./dist/*.js" + }, + "./*": { + "import": "./dist/*.mjs", + "require": "./dist/*.js" + } + } +} diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md new file mode 100644 index 00000000..b3d1e82f --- /dev/null +++ b/packages/mcp-server/README.md @@ -0,0 +1,332 @@ +# Terminal49 TypeScript MCP Server + +It is generated with [Stainless](https://www.stainless.com/). + +## Installation + +### Building + +Because it's not published yet, clone the repo and build it: + +```sh +git clone git@github.com:Terminal49/API.git +cd API +./scripts/bootstrap +./scripts/build +``` + +### Running + +```sh +# set env vars as needed +export TERMINAL49_API_KEY="My API Key" +node ./packages/mcp-server/dist/index.js +``` + +> [!NOTE] +> Once this package is [published to npm](https://www.stainless.com/docs/guides/publish), this will become: `npx -y terminal49-mcp` + +### Via MCP Client + +[Build the project](#building) as mentioned above. + +There is a partial list of existing clients at [modelcontextprotocol.io](https://modelcontextprotocol.io/clients). If you already +have a client, consult their documentation to install the MCP server. + +For clients with a configuration JSON, it might look something like this: + +```json +{ + "mcpServers": { + "terminal49_api": { + "command": "node", + "args": ["/path/to/local/API/packages/mcp-server", "--client=claude", "--tools=dynamic"], + "env": { + "TERMINAL49_API_KEY": "My API Key" + } + } + } +} +``` + +## Exposing endpoints to your MCP Client + +There are three ways to expose endpoints as tools in the MCP server: + +1. Exposing one tool per endpoint, and filtering as necessary +2. Exposing a set of tools to dynamically discover and invoke endpoints from the API +3. Exposing a docs search tool and a code execution tool, allowing the client to write code to be executed against the TypeScript client + +### Filtering endpoints and tools + +You can run the package on the command line to discover and filter the set of tools that are exposed by the +MCP Server. This can be helpful for large APIs where including all endpoints at once is too much for your AI's +context window. + +You can filter by multiple aspects: + +- `--tool` includes a specific tool by name +- `--resource` includes all tools under a specific resource, and can have wildcards, e.g. `my.resource*` +- `--operation` includes just read (get/list) or just write operations + +### Dynamic tools + +If you specify `--tools=dynamic` to the MCP server, instead of exposing one tool per endpoint in the API, it will +expose the following tools: + +1. `list_api_endpoints` - Discovers available endpoints, with optional filtering by search query +2. `get_api_endpoint_schema` - Gets detailed schema information for a specific endpoint +3. `invoke_api_endpoint` - Executes any endpoint with the appropriate parameters + +This allows you to have the full set of API endpoints available to your MCP Client, while not requiring that all +of their schemas be loaded into context at once. Instead, the LLM will automatically use these tools together to +search for, look up, and invoke endpoints dynamically. However, due to the indirect nature of the schemas, it +can struggle to provide the correct properties a bit more than when tools are imported explicitly. Therefore, +you can opt-in to explicit tools, the dynamic tools, or both. + +See more information with `--help`. + +All of these command-line options can be repeated, combined together, and have corresponding exclusion versions (e.g. `--no-tool`). + +Use `--list` to see the list of available tools, or see below. + +### Code execution + +If you specify `--tools=code` to the MCP server, it will expose just two tools: + +- `search_docs` - Searches the API documentation and returns a list of markdown results +- `execute` - Runs code against the TypeScript client + +This allows the LLM to implement more complex logic by chaining together many API calls without loading +intermediary results into its context window. + +The code execution itself happens in a Deno sandbox that has network access only to the base URL for the API. + +### Specifying the MCP Client + +Different clients have varying abilities to handle arbitrary tools and schemas. + +You can specify the client you are using with the `--client` argument, and the MCP server will automatically +serve tools and schemas that are more compatible with that client. + +- `--client=`: Set all capabilities based on a known MCP client + + - Valid values: `openai-agents`, `claude`, `claude-code`, `cursor` + - Example: `--client=cursor` + +Additionally, if you have a client not on the above list, or the client has gotten better +over time, you can manually enable or disable certain capabilities: + +- `--capability=`: Specify individual client capabilities + - Available capabilities: + - `top-level-unions`: Enable support for top-level unions in tool schemas + - `valid-json`: Enable JSON string parsing for arguments + - `refs`: Enable support for $ref pointers in schemas + - `unions`: Enable support for union types (anyOf) in schemas + - `formats`: Enable support for format validations in schemas (e.g. date-time, email) + - `tool-name-length=N`: Set maximum tool name length to N characters + - Example: `--capability=top-level-unions --capability=tool-name-length=40` + - Example: `--capability=top-level-unions,tool-name-length=40` + +### Examples + +1. Filter for read operations on cards: + +```bash +--resource=cards --operation=read +``` + +2. Exclude specific tools while including others: + +```bash +--resource=cards --no-tool=create_cards +``` + +3. Configure for Cursor client with custom max tool name length: + +```bash +--client=cursor --capability=tool-name-length=40 +``` + +4. Complex filtering with multiple criteria: + +```bash +--resource=cards,accounts --operation=read --tag=kyc --no-tool=create_cards +``` + +## Running remotely + +Launching the client with `--transport=http` launches the server as a remote server using Streamable HTTP transport. The `--port` setting can choose the port it will run on, and the `--socket` setting allows it to run on a Unix socket. + +Authorization can be provided via the following headers: +| Header | Equivalent client option | Security scheme | +| ---------------------- | ------------------------ | --------------- | +| `x-terminal49-api-key` | `apiKey` | authorization | + +A configuration JSON for this server might look like this, assuming the server is hosted at `http://localhost:3000`: + +```json +{ + "mcpServers": { + "terminal49_api": { + "url": "http://localhost:3000", + "headers": { + "x-terminal49-api-key": "My API Key" + } + } + } +} +``` + +The command-line arguments for filtering tools and specifying clients can also be used as query parameters in the URL. +For example, to exclude specific tools while including others, use the URL: + +``` +http://localhost:3000?resource=cards&resource=accounts&no_tool=create_cards +``` + +Or, to configure for the Cursor client, with a custom max tool name length, use the URL: + +``` +http://localhost:3000?client=cursor&capability=tool-name-length%3D40 +``` + +## Importing the tools and server individually + +```js +// Import the server, generated endpoints, or the init function +import { server, endpoints, init } from "terminal49-mcp/server"; + +// import a specific tool +import retrieveShipments from "terminal49-mcp/tools/shipments/retrieve-shipments"; + +// initialize the server and all endpoints +init({ server, endpoints }); + +// manually start server +const transport = new StdioServerTransport(); +await server.connect(transport); + +// or initialize your own server with specific tools +const myServer = new McpServer(...); + +// define your own endpoint +const myCustomEndpoint = { + tool: { + name: 'my_custom_tool', + description: 'My custom tool', + inputSchema: zodToJsonSchema(z.object({ a_property: z.string() })), + }, + handler: async (client: client, args: any) => { + return { myResponse: 'Hello world!' }; + }) +}; + +// initialize the server with your custom endpoints +init({ server: myServer, endpoints: [retrieveShipments, myCustomEndpoint] }); +``` + +## Available Tools + +The following tools are available in this MCP server. + +### Resource `shipments`: + +- `retrieve_shipments` (`read`): Retrieves the details of an existing shipment. You need only supply the unique shipment `id` that was returned upon `tracking_request` creation. +- `update_shipments` (`write`): Update a shipment +- `list_shipments` (`read`): Returns a list of your shipments. The shipments are returned sorted by creation date, with the most recent shipments appearing first. + + This api will return all shipments associated with the account. Shipments created via the `tracking_request` API aswell as the ones added via the dashboard will be retuned via this endpoint. + +- `resume_tracking_shipments` (`write`): Resume tracking a shipment. Keep in mind that some information is only made available by our data sources at specific times, so a stopped and resumed shipment may have some information missing. +- `stop_tracking_shipments` (`write`): We'll stop tracking the shipment, which means that there will be no more updates. You can still access the shipment's previously-collected information via the API or dashboard. + + You can resume tracking a shipment by calling the `resume_tracking` endpoint, but keep in mind that some information is only made available by our data sources at specific times, so a stopped and resumed shipment may have some information missing. + +### Resource `tracking_requests`: + +- `create_tracking_requests` (`write`): To track an ocean shipment, you create a new tracking request. + Two attributes are required to track a shipment. A `bill of lading/booking number` and a shipping line `SCAC`. + + Once a tracking request is created we will attempt to fetch the shipment details and it's related containers from the shipping line. If the attempt is successful we will create in new shipment object including any related container objects. We will send a `tracking_request.succeeded` webhook notification to your webhooks. + + If the attempt to fetch fails then we will send a `tracking_request.failed` webhook notification to your `webhooks`. + + A `tracking_request.succeeded` or `tracking_request.failed` webhook notificaiton will only be sent if you have atleast one active webhook. + +- `retrieve_tracking_requests` (`read`): Get the details and status of an existing tracking request. +- `update_tracking_requests` (`write`): Update a tracking request +- `list_tracking_requests` (`read`): Returns a list of your tracking requests. The tracking requests are returned sorted by creation date, with the most recent tracking request appearing first. + +### Resource `webhooks`: + +- `create_webhooks` (`write`): You can configure a webhook via the API to be notified about events that happen in your Terminal49 account. These events can be realted to tracking_requests, shipments and containers. + + This is the recommended way tracking shipments and containers via the API. You should use this instead of polling our the API periodically. + +- `retrieve_webhooks` (`read`): Get the details of a single webhook +- `update_webhooks` (`write`): Update a single webhook +- `list_webhooks` (`read`): Get a list of all the webhooks +- `delete_webhooks` (`write`): Delete a webhook +- `list_ips_webhooks` (`read`): Return the list of IPs used for sending webhook notifications. This can be useful for whitelisting the IPs on the firewall. + +### Resource `webhook_notifications`: + +- `retrieve_webhook_notifications` (`read`): +- `list_webhook_notifications` (`read`): Return the list of webhook notifications. This can be useful for reconciling your data if your endpoint has been down. +- `get_examples_webhook_notifications` (`read`): Returns an example payload as it would be sent to a webhook endpoint for the provided `event` + +### Resource `containers`: + +- `retrieve_containers` (`read`): Retrieves the details of a container. +- `update_containers` (`write`): Update a container +- `list_containers` (`read`): Returns a list of container. The containers are returned sorted by creation date, with the most recently refreshed containers appearing first. + + This API will return all containers associated with the account. + +- `get_raw_events_containers` (`read`): #### Deprecation warning + The `raw_events` endpoint is provided as-is. + + For past events we recommend consuming `transport_events`. + + *** + + Get a list of past and future (estimated) milestones for a container as reported by the carrier. Some of the data is normalized even though the API is called raw_events. + + Normalized attributes: `event` and `timestamp` timestamp. Not all of the `event` values have been normalized. You can expect the the events related to container movements to be normalized but there are cases where events are not normalized. + + For past historical events we recommend consuming `transport_events`. Although there are fewer events here those events go through additional vetting and normalization to avoid false positives and get you correct data. + +- `get_transport_events_containers` (`read`): Get a list of past transport events (canonical) for a container. All data has been normalized across all carriers. These are a verified subset of the raw events may also be sent as Webhook Notifications to a webhook endpoint. + + This does not provide any estimated future events. See `container/:id/raw_events` endpoint for that. + +### Resource `shipping_lines`: + +- `retrieve_shipping_lines` (`read`): Return the details of a single shipping line. +- `list_shipping_lines` (`read`): Return a list of shipping lines supported by Terminal49. + N.B. There is no pagination for this endpoint. + +### Resource `metro_areas`: + +- `retrieve_metro_areas` (`read`): Return the details of a single metro area. + +### Resource `ports`: + +- `retrieve_ports` (`read`): Return the details of a single port. + +### Resource `vessels`: + +- `retrieve_by_id_vessels` (`read`): Returns a vessel by it's given identifier +- `retrieve_by_imo_vessels` (`read`): Returns a vessel by the given IMO number. + +### Resource `terminals`: + +- `retrieve_terminals` (`read`): Return the details of a single terminal. + +### Resource `parties`: + +- `create_parties` (`write`): Creates a new party +- `retrieve_parties` (`read`): Returns a party by it's given identifier +- `update_parties` (`write`): Updates a party +- `list_parties` (`read`): Get a list of parties diff --git a/packages/mcp-server/build b/packages/mcp-server/build new file mode 100644 index 00000000..14eaee43 --- /dev/null +++ b/packages/mcp-server/build @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -exuo pipefail + +rm -rf dist; mkdir dist + +# Copy src to dist/src and build from dist/src into dist, so that +# the source map for index.js.map will refer to ./src/index.ts etc +cp -rp src README.md dist + +for file in LICENSE; do + if [ -e "../../${file}" ]; then cp "../../${file}" dist; fi +done + +for file in CHANGELOG.md; do + if [ -e "${file}" ]; then cp "${file}" dist; fi +done + +# this converts the export map paths for the dist directory +# and does a few other minor things +PKG_JSON_PATH=../../packages/mcp-server/package.json node ../../scripts/utils/make-dist-package-json.cjs > dist/package.json + +# updates the `terminal49` dependency to point to NPM +node scripts/postprocess-dist-package-json.cjs + +# build to .js/.mjs/.d.ts files +./node_modules/.bin/tsc-multi + +cp tsconfig.dist-src.json dist/src/tsconfig.json + +chmod +x dist/index.js + +DIST_PATH=./dist PKG_IMPORT_PATH=terminal49-mcp/ node ../../scripts/utils/postprocess-files.cjs + +# mcp bundle +rm -rf dist-bundle terminal49_api.mcpb; mkdir dist-bundle + +# copy package.json +PKG_JSON_PATH=../../packages/mcp-server/package.json node ../../scripts/utils/make-dist-package-json.cjs > dist-bundle/package.json + +# copy files +node scripts/copy-bundle-files.cjs + +# install runtime deps +cd dist-bundle +npm install +cd .. + +# pack bundle +cp manifest.json dist-bundle + +npx mcpb pack dist-bundle terminal49_api.mcpb + +npx mcpb sign terminal49_api.mcpb --self-signed + +# clean up +rm -rf dist-bundle diff --git a/packages/mcp-server/jest.config.ts b/packages/mcp-server/jest.config.ts new file mode 100644 index 00000000..9e0ca7c8 --- /dev/null +++ b/packages/mcp-server/jest.config.ts @@ -0,0 +1,17 @@ +import type { JestConfigWithTsJest } from 'ts-jest'; + +const config: JestConfigWithTsJest = { + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', + transform: { + '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }], + }, + moduleNameMapper: { + '^terminal49-mcp$': '/src/index.ts', + '^terminal49-mcp/(.*)$': '/src/$1', + }, + modulePathIgnorePatterns: ['/dist/'], + testPathIgnorePatterns: ['scripts'], +}; + +export default config; diff --git a/packages/mcp-server/manifest.json b/packages/mcp-server/manifest.json new file mode 100644 index 00000000..ab48adc4 --- /dev/null +++ b/packages/mcp-server/manifest.json @@ -0,0 +1,43 @@ +{ + "dxt_version": "0.2", + "name": "terminal49-mcp", + "version": "0.0.1-alpha.0", + "description": "The official MCP Server for the Terminal49 API", + "author": { + "name": "Terminal49", + "email": "support@terminal49.com" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Terminal49/API.git" + }, + "homepage": "https://github.com/Terminal49/API/tree/main/packages/mcp-server#readme", + "documentation": "https://www.terminal49.com", + "server": { + "type": "node", + "entry_point": "index.js", + "mcp_config": { + "command": "node", + "args": ["${__dirname}/index.js"], + "env": { + "TERMINAL49_API_KEY": "${user_config.TERMINAL49_API_KEY}" + } + } + }, + "user_config": { + "TERMINAL49_API_KEY": { + "title": "api_key", + "description": "`Token YOUR_API_TOKEN`\n\nThe APIs require authentication to be done using header-based API Key and Secret Authentication. \n\nAPI key and secret are sent va the `Authorization` request header.\n\nYou send your API key and secret in the following way:\n\n`Authorization: Token YOUR_API_KEY`", + "required": true, + "type": "string" + } + }, + "tools": [], + "tools_generated": true, + "compatibility": { + "runtimes": { + "node": ">=18.0.0" + } + }, + "keywords": ["api"] +} diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json new file mode 100644 index 00000000..537f26dd --- /dev/null +++ b/packages/mcp-server/package.json @@ -0,0 +1,87 @@ +{ + "name": "terminal49-mcp", + "version": "0.1.0-alpha.1", + "description": "The official MCP Server for the Terminal49 API", + "author": "Terminal49 ", + "types": "dist/index.d.ts", + "main": "dist/index.js", + "type": "commonjs", + "repository": { + "type": "git", + "url": "git+https://github.com/Terminal49/API.git", + "directory": "packages/mcp-server" + }, + "homepage": "https://github.com/Terminal49/API/tree/main/packages/mcp-server#readme", + "license": "Apache-2.0", + "packageManager": "yarn@1.22.22", + "private": false, + "publishConfig": { + "access": "public" + }, + "scripts": { + "test": "jest", + "build": "bash ./build", + "prepack": "echo 'to pack, run yarn build && (cd dist; yarn pack)' && exit 1", + "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", + "format": "prettier --write --cache --cache-strategy metadata . !dist", + "prepare": "npm run build", + "tsn": "ts-node -r tsconfig-paths/register", + "lint": "eslint --ext ts,js .", + "fix": "eslint --fix --ext ts,js ." + }, + "dependencies": { + "terminal49": "file:../../dist/", + "@cloudflare/cabidela": "^0.2.4", + "@modelcontextprotocol/sdk": "^1.11.5", + "@valtown/deno-http-worker": "^0.0.21", + "cors": "^2.8.5", + "express": "^5.1.0", + "fuse.js": "^7.1.0", + "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz", + "qs": "^6.14.0", + "typescript": "5.8.3", + "yargs": "^17.7.2", + "zod": "^3.25.20", + "zod-to-json-schema": "^3.24.5", + "zod-validation-error": "^4.0.1" + }, + "bin": { + "mcp-server": "dist/index.js" + }, + "devDependencies": { + "@anthropic-ai/mcpb": "1.1.0", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.3", + "@types/jest": "^29.4.0", + "@types/qs": "^6.14.0", + "@types/yargs": "^17.0.8", + "@typescript-eslint/eslint-plugin": "8.31.1", + "@typescript-eslint/parser": "8.31.1", + "eslint": "^8.49.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "jest": "^29.4.0", + "prettier": "^3.0.0", + "ts-jest": "^29.1.0", + "ts-morph": "^19.0.0", + "ts-node": "^10.5.0", + "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz", + "tsconfig-paths": "^4.0.0" + }, + "imports": { + "terminal49-mcp": ".", + "terminal49-mcp/*": "./src/*" + }, + "exports": { + ".": { + "require": "./dist/index.js", + "default": "./dist/index.mjs" + }, + "./*.mjs": "./dist/*.mjs", + "./*.js": "./dist/*.js", + "./*": { + "require": "./dist/*.js", + "default": "./dist/*.mjs" + } + } +} diff --git a/packages/mcp-server/scripts/copy-bundle-files.cjs b/packages/mcp-server/scripts/copy-bundle-files.cjs new file mode 100644 index 00000000..b47c0a10 --- /dev/null +++ b/packages/mcp-server/scripts/copy-bundle-files.cjs @@ -0,0 +1,36 @@ +const fs = require('fs'); +const path = require('path'); +const pkgJson = require('../dist-bundle/package.json'); + +const distDir = path.resolve(__dirname, '..', 'dist'); +const distBundleDir = path.resolve(__dirname, '..', 'dist-bundle'); +const distBundlePkgJson = path.join(distBundleDir, 'package.json'); + +async function* walk(dir) { + for await (const d of await fs.promises.opendir(dir)) { + const entry = path.join(dir, d.name); + if (d.isDirectory()) yield* walk(entry); + else if (d.isFile()) yield entry; + } +} + +async function copyFiles() { + // copy runtime files + for await (const file of walk(distDir)) { + if (!/[cm]?js$/.test(file)) continue; + const dest = path.join(distBundleDir, path.relative(distDir, file)); + await fs.promises.mkdir(path.dirname(dest), { recursive: true }); + await fs.promises.copyFile(file, dest); + } + + // replace package.json reference with local reference + for (const dep in pkgJson.dependencies) { + if (dep === 'terminal49') { + pkgJson.dependencies[dep] = 'file:../../../dist/'; + } + } + + await fs.promises.writeFile(distBundlePkgJson, JSON.stringify(pkgJson, null, 2)); +} + +copyFiles(); diff --git a/packages/mcp-server/scripts/postprocess-dist-package-json.cjs b/packages/mcp-server/scripts/postprocess-dist-package-json.cjs new file mode 100644 index 00000000..1bae89ad --- /dev/null +++ b/packages/mcp-server/scripts/postprocess-dist-package-json.cjs @@ -0,0 +1,12 @@ +const fs = require('fs'); +const pkgJson = require('../dist/package.json'); +const parentPkgJson = require('../../../package.json'); + +for (const dep in pkgJson.dependencies) { + // ensure we point to NPM instead of a local directory + if (dep === 'terminal49') { + pkgJson.dependencies[dep] = '^' + parentPkgJson.version; + } +} + +fs.writeFileSync('dist/package.json', JSON.stringify(pkgJson, null, 2)); diff --git a/packages/mcp-server/src/code-tool-paths.cts b/packages/mcp-server/src/code-tool-paths.cts new file mode 100644 index 00000000..15ce7f55 --- /dev/null +++ b/packages/mcp-server/src/code-tool-paths.cts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export const workerPath = require.resolve('./code-tool-worker.mjs'); diff --git a/packages/mcp-server/src/code-tool-types.ts b/packages/mcp-server/src/code-tool-types.ts new file mode 100644 index 00000000..60aafb55 --- /dev/null +++ b/packages/mcp-server/src/code-tool-types.ts @@ -0,0 +1,14 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { ClientOptions } from 'terminal49'; + +export type WorkerInput = { + opts: ClientOptions; + code: string; +}; +export type WorkerSuccess = { + result: unknown | null; + logLines: string[]; + errLines: string[]; +}; +export type WorkerError = { message: string | undefined }; diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts new file mode 100644 index 00000000..a032661c --- /dev/null +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -0,0 +1,228 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import util from 'node:util'; + +import Fuse from 'fuse.js'; +import ts from 'typescript'; + +import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types'; +import { Terminal49 } from 'terminal49'; + +function getRunFunctionNode( + code: string, +): ts.FunctionDeclaration | ts.FunctionExpression | ts.ArrowFunction | null { + const sourceFile = ts.createSourceFile('code.ts', code, ts.ScriptTarget.Latest, true); + + for (const statement of sourceFile.statements) { + // Check for top-level function declarations + if (ts.isFunctionDeclaration(statement)) { + if (statement.name?.text === 'run') { + return statement; + } + } + + // Check for variable declarations: const run = () => {} or const run = function() {} + if (ts.isVariableStatement(statement)) { + for (const declaration of statement.declarationList.declarations) { + if (ts.isIdentifier(declaration.name) && declaration.name.text === 'run') { + // Check if it's initialized with a function + if ( + declaration.initializer && + (ts.isFunctionExpression(declaration.initializer) || ts.isArrowFunction(declaration.initializer)) + ) { + return declaration.initializer; + } + } + } + } + } + + return null; +} + +const fuse = new Fuse( + [ + 'client.shipments.list', + 'client.shipments.resumeTracking', + 'client.shipments.retrieve', + 'client.shipments.stopTracking', + 'client.shipments.update', + 'client.trackingRequests.create', + 'client.trackingRequests.list', + 'client.trackingRequests.retrieve', + 'client.trackingRequests.update', + 'client.webhooks.create', + 'client.webhooks.delete', + 'client.webhooks.list', + 'client.webhooks.listIPs', + 'client.webhooks.retrieve', + 'client.webhooks.update', + 'client.webhookNotifications.getExamples', + 'client.webhookNotifications.list', + 'client.webhookNotifications.retrieve', + 'client.containers.getRawEvents', + 'client.containers.getTransportEvents', + 'client.containers.list', + 'client.containers.retrieve', + 'client.containers.update', + 'client.shippingLines.list', + 'client.shippingLines.retrieve', + 'client.metroAreas.retrieve', + 'client.ports.retrieve', + 'client.vessels.retrieveByID', + 'client.vessels.retrieveByImo', + 'client.terminals.retrieve', + 'client.parties.create', + 'client.parties.list', + 'client.parties.retrieve', + 'client.parties.update', + ], + { threshold: 1, shouldSort: true }, +); + +function getMethodSuggestions(fullyQualifiedMethodName: string): string[] { + return fuse + .search(fullyQualifiedMethodName) + .map(({ item }) => item) + .slice(0, 5); +} + +const proxyToObj = new WeakMap(); +const objToProxy = new WeakMap(); + +type ClientProxyConfig = { + path: string[]; + isBelievedBad?: boolean; +}; + +function makeSdkProxy(obj: T, { path, isBelievedBad = false }: ClientProxyConfig): T { + let proxy: T = objToProxy.get(obj); + + if (!proxy) { + proxy = new Proxy(obj, { + get(target, prop, receiver) { + const propPath = [...path, String(prop)]; + const value = Reflect.get(target, prop, receiver); + + if (isBelievedBad || (!(prop in target) && value === undefined)) { + // If we're accessing a path that doesn't exist, it will probably eventually error. + // Let's proxy it and mark it bad so that we can control the error message. + // We proxy an empty class so that an invocation or construction attempt is possible. + return makeSdkProxy(class {}, { path: propPath, isBelievedBad: true }); + } + + if (value !== null && (typeof value === 'object' || typeof value === 'function')) { + return makeSdkProxy(value, { path: propPath, isBelievedBad }); + } + + return value; + }, + + apply(target, thisArg, args) { + if (isBelievedBad || typeof target !== 'function') { + const fullyQualifiedMethodName = path.join('.'); + const suggestions = getMethodSuggestions(fullyQualifiedMethodName); + throw new Error( + `${fullyQualifiedMethodName} is not a function. Did you mean: ${suggestions.join(', ')}`, + ); + } + + return Reflect.apply(target, proxyToObj.get(thisArg) ?? thisArg, args); + }, + + construct(target, args, newTarget) { + if (isBelievedBad || typeof target !== 'function') { + const fullyQualifiedMethodName = path.join('.'); + const suggestions = getMethodSuggestions(fullyQualifiedMethodName); + throw new Error( + `${fullyQualifiedMethodName} is not a constructor. Did you mean: ${suggestions.join(', ')}`, + ); + } + + return Reflect.construct(target, args, newTarget); + }, + }); + + objToProxy.set(obj, proxy); + proxyToObj.set(proxy, obj); + } + + return proxy; +} + +function parseError(code: string, error: unknown): string | undefined { + if (!(error instanceof Error)) return; + const message = error.name ? `${error.name}: ${error.message}` : error.message; + try { + // Deno uses V8; the first ":LINE:COLUMN" is the top of stack. + const lineNumber = error.stack?.match(/:([0-9]+):[0-9]+/)?.[1]; + // -1 for the zero-based indexing + const line = + lineNumber && + code + .split('\n') + .at(parseInt(lineNumber, 10) - 1) + ?.trim(); + return line ? `${message}\n at line ${lineNumber}\n ${line}` : message; + } catch { + return message; + } +} + +const fetch = async (req: Request): Promise => { + const { opts, code } = (await req.json()) as WorkerInput; + if (code == null) { + return Response.json( + { + message: + 'The code param is missing. Provide one containing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```', + } satisfies WorkerError, + { status: 400, statusText: 'Code execution error' }, + ); + } + + const runFunctionNode = getRunFunctionNode(code); + if (!runFunctionNode) { + return Response.json( + { + message: + 'The code is missing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```', + } satisfies WorkerError, + { status: 400, statusText: 'Code execution error' }, + ); + } + + const client = new Terminal49({ + ...opts, + }); + + const logLines: string[] = []; + const errLines: string[] = []; + const console = { + log: (...args: unknown[]) => { + logLines.push(util.format(...args)); + }, + error: (...args: unknown[]) => { + errLines.push(util.format(...args)); + }, + }; + try { + let run_ = async (client: any) => {}; + eval(`${code}\nrun_ = run;`); + const result = await run_(makeSdkProxy(client, { path: ['client'] })); + return Response.json({ + result, + logLines, + errLines, + } satisfies WorkerSuccess); + } catch (e) { + return Response.json( + { + message: parseError(code, e), + } satisfies WorkerError, + { status: 400, statusText: 'Code execution error' }, + ); + } +}; + +export default { fetch }; diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts new file mode 100644 index 00000000..cc4c4377 --- /dev/null +++ b/packages/mcp-server/src/code-tool.ts @@ -0,0 +1,147 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { dirname } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import Terminal49, { ClientOptions } from 'terminal49'; +import { ContentBlock, Endpoint, Metadata, ToolCallResult } from './tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; + +import { WorkerInput, WorkerError, WorkerSuccess } from './code-tool-types'; + +/** + * A tool that runs code against a copy of the SDK. + * + * Instead of exposing every endpoint as its own tool, which uses up too many tokens for LLMs to use at once, + * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then + * a generic endpoint that can be used to invoke any endpoint with the provided arguments. + * + * @param endpoints - The endpoints to include in the list. + */ +export async function codeTool(): Promise { + const metadata: Metadata = { resource: 'all', operation: 'write', tags: [] }; + const tool: Tool = { + name: 'execute', + description: + 'Runs JavaScript code to interact with the API.\n\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client named "client", and it will be run.\nWrite code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```\n\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', + inputSchema: { type: 'object', properties: { code: { type: 'string' } } }, + }; + + // Import dynamically to avoid failing at import time in cases where the environment is not well-supported. + const { newDenoHTTPWorker } = await import('@valtown/deno-http-worker'); + const { workerPath } = await import('./code-tool-paths.cjs'); + + const handler = async (client: Terminal49, args: unknown): Promise => { + const baseURLHostname = new URL(client.baseURL).hostname; + const { code } = args as { code: string }; + + const worker = await newDenoHTTPWorker(pathToFileURL(workerPath), { + runFlags: [ + `--node-modules-dir=manual`, + `--allow-read=code-tool-worker.mjs,${workerPath.replace(/([\/\\]node_modules)[\/\\].+$/, '$1')}/`, + `--allow-net=${baseURLHostname}`, + // Allow environment variables because instantiating the client will try to read from them, + // even though they are not set. + '--allow-env', + ], + printOutput: true, + spawnOptions: { + cwd: dirname(workerPath), + }, + }); + + try { + const resp = await new Promise((resolve, reject) => { + worker.addEventListener('exit', (exitCode) => { + reject(new Error(`Worker exited with code ${exitCode}`)); + }); + + const opts: ClientOptions = { + baseURL: client.baseURL, + apiKey: client.apiKey, + defaultHeaders: { + 'X-Stainless-MCP': 'true', + }, + }; + + const req = worker.request( + 'http://localhost', + { + headers: { + 'content-type': 'application/json', + }, + method: 'POST', + }, + (resp) => { + const body: Uint8Array[] = []; + resp.on('error', (err) => { + reject(err); + }); + resp.on('data', (chunk) => { + body.push(chunk); + }); + resp.on('end', () => { + resolve( + new Response(Buffer.concat(body).toString(), { + status: resp.statusCode ?? 200, + headers: resp.headers as any, + }), + ); + }); + }, + ); + + const body = JSON.stringify({ + opts, + code, + } satisfies WorkerInput); + + req.write(body, (err) => { + if (err != null) { + reject(err); + } + }); + + req.end(); + }); + + if (resp.status === 200) { + const { result, logLines, errLines } = (await resp.json()) as WorkerSuccess; + const returnOutput: ContentBlock | null = + result == null ? null : ( + { + type: 'text', + text: typeof result === 'string' ? result : JSON.stringify(result), + } + ); + const logOutput: ContentBlock | null = + logLines.length === 0 ? + null + : { + type: 'text', + text: logLines.join('\n'), + }; + const errOutput: ContentBlock | null = + errLines.length === 0 ? + null + : { + type: 'text', + text: 'Error output:\n' + errLines.join('\n'), + }; + return { + content: [returnOutput, logOutput, errOutput].filter((block) => block !== null), + }; + } else { + const { message } = (await resp.json()) as WorkerError; + return { + content: message == null ? [] : [{ type: 'text', text: message }], + isError: true, + }; + } + } finally { + worker.terminate(); + } + }; + + return { metadata, tool, handler }; +} diff --git a/packages/mcp-server/src/compat.ts b/packages/mcp-server/src/compat.ts new file mode 100644 index 00000000..f84053c7 --- /dev/null +++ b/packages/mcp-server/src/compat.ts @@ -0,0 +1,483 @@ +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import { z } from 'zod'; +import { Endpoint } from './tools'; + +export interface ClientCapabilities { + topLevelUnions: boolean; + validJson: boolean; + refs: boolean; + unions: boolean; + formats: boolean; + toolNameLength: number | undefined; +} + +export const defaultClientCapabilities: ClientCapabilities = { + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, +}; + +export const ClientType = z.enum(['openai-agents', 'claude', 'claude-code', 'cursor', 'infer']); +export type ClientType = z.infer; + +// Client presets for compatibility +// Note that these could change over time as models get better, so this is +// a best effort. +export const knownClients: Record, ClientCapabilities> = { + 'openai-agents': { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }, + claude: { + topLevelUnions: true, + validJson: false, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }, + 'claude-code': { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }, + cursor: { + topLevelUnions: false, + validJson: true, + refs: false, + unions: false, + formats: false, + toolNameLength: 50, + }, +}; + +/** + * Attempts to parse strings into JSON objects + */ +export function parseEmbeddedJSON(args: Record, schema: Record) { + let updated = false; + const newArgs: Record = Object.assign({}, args); + + for (const [key, value] of Object.entries(newArgs)) { + if (typeof value === 'string') { + try { + const parsed = JSON.parse(value); + // Only parse if result is a plain object (not array, null, or primitive) + if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + newArgs[key] = parsed; + updated = true; + } + } catch (e) { + // Not valid JSON, leave as is + } + } + } + + if (updated) { + return newArgs; + } + + return args; +} + +export type JSONSchema = { + type?: string; + properties?: Record; + required?: string[]; + anyOf?: JSONSchema[]; + $ref?: string; + $defs?: Record; + [key: string]: any; +}; + +/** + * Truncates tool names to the specified length while ensuring uniqueness. + * If truncation would cause duplicate names, appends a number to make them unique. + */ +export function truncateToolNames(names: string[], maxLength: number): Map { + if (maxLength <= 0) { + return new Map(); + } + + const renameMap = new Map(); + const usedNames = new Set(); + + const toTruncate = names.filter((name) => name.length > maxLength); + + if (toTruncate.length === 0) { + return renameMap; + } + + const willCollide = + new Set(toTruncate.map((name) => name.slice(0, maxLength - 1))).size < toTruncate.length; + + if (!willCollide) { + for (const name of toTruncate) { + const truncatedName = name.slice(0, maxLength); + renameMap.set(name, truncatedName); + } + } else { + const baseLength = maxLength - 1; + + for (const name of toTruncate) { + const baseName = name.slice(0, baseLength); + let counter = 1; + + while (usedNames.has(baseName + counter)) { + counter++; + } + + const finalName = baseName + counter; + renameMap.set(name, finalName); + usedNames.add(finalName); + } + } + + return renameMap; +} + +/** + * Removes top-level unions from a tool by splitting it into multiple tools, + * one for each variant in the union. + */ +export function removeTopLevelUnions(tool: Tool): Tool[] { + const inputSchema = tool.inputSchema as JSONSchema; + const variants = inputSchema.anyOf; + + if (!variants || !Array.isArray(variants) || variants.length === 0) { + return [tool]; + } + + const defs = inputSchema.$defs || {}; + + return variants.map((variant, index) => { + const variantSchema: JSONSchema = { + ...inputSchema, + ...variant, + type: 'object', + properties: { + ...(inputSchema.properties || {}), + ...(variant.properties || {}), + }, + }; + + delete variantSchema.anyOf; + + if (!variantSchema['description']) { + variantSchema['description'] = tool.description; + } + + const usedDefs = findUsedDefs(variant, defs); + if (Object.keys(usedDefs).length > 0) { + variantSchema.$defs = usedDefs; + } else { + delete variantSchema.$defs; + } + + return { + ...tool, + name: `${tool.name}_${toSnakeCase(variant['title'] || `variant${index + 1}`)}`, + description: variant['description'] || tool.description, + inputSchema: variantSchema, + } as Tool; + }); +} + +function findUsedDefs( + schema: JSONSchema, + defs: Record, + visited: Set = new Set(), +): Record { + const usedDefs: Record = {}; + + if (typeof schema !== 'object' || schema === null) { + return usedDefs; + } + + if (schema.$ref) { + const refParts = schema.$ref.split('/'); + if (refParts[0] === '#' && refParts[1] === '$defs' && refParts[2]) { + const defName = refParts[2]; + const def = defs[defName]; + if (def && !visited.has(schema.$ref)) { + usedDefs[defName] = def; + visited.add(schema.$ref); + Object.assign(usedDefs, findUsedDefs(def, defs, visited)); + visited.delete(schema.$ref); + } + } + return usedDefs; + } + + for (const key in schema) { + if (key !== '$defs' && typeof schema[key] === 'object' && schema[key] !== null) { + Object.assign(usedDefs, findUsedDefs(schema[key] as JSONSchema, defs, visited)); + } + } + + return usedDefs; +} + +// Export for testing +export { findUsedDefs }; + +/** + * Inlines all $refs in a schema, eliminating $defs. + * If a circular reference is detected, the circular property is removed. + */ +export function inlineRefs(schema: JSONSchema): JSONSchema { + if (!schema || typeof schema !== 'object') { + return schema; + } + + const clonedSchema = { ...schema }; + const defs: Record = schema.$defs || {}; + + delete clonedSchema.$defs; + + const result = inlineRefsRecursive(clonedSchema, defs, new Set()); + // The top level can never be null + return result === null ? {} : result; +} + +function inlineRefsRecursive( + schema: JSONSchema, + defs: Record, + refPath: Set, +): JSONSchema | null { + if (!schema || typeof schema !== 'object') { + return schema; + } + + if (Array.isArray(schema)) { + return schema.map((item) => { + const processed = inlineRefsRecursive(item, defs, refPath); + return processed === null ? {} : processed; + }) as JSONSchema; + } + + const result = { ...schema }; + + if ('$ref' in result && typeof result.$ref === 'string') { + if (result.$ref.startsWith('#/$defs/')) { + const refName = result.$ref.split('/').pop() as string; + const def = defs[refName]; + + // If we've already seen this ref in our path, we have a circular reference + if (refPath.has(result.$ref)) { + // For circular references, we completely remove the property + // by returning null. The parent will remove it. + return null; + } + + if (def) { + const newRefPath = new Set(refPath); + newRefPath.add(result.$ref); + + const inlinedDef = inlineRefsRecursive({ ...def }, defs, newRefPath); + + if (inlinedDef === null) { + return { ...result }; + } + + // Merge the inlined definition with the original schema's properties + // but preserve things like description, etc. + const { $ref, ...rest } = result; + return { ...inlinedDef, ...rest }; + } + } + + // Keep external refs as-is + return result; + } + + for (const key in result) { + if (result[key] && typeof result[key] === 'object') { + const processed = inlineRefsRecursive(result[key] as JSONSchema, defs, refPath); + if (processed === null) { + // Remove properties that would cause circular references + delete result[key]; + } else { + result[key] = processed; + } + } + } + + return result; +} + +/** + * Removes anyOf fields from a schema, using only the first variant. + */ +export function removeAnyOf(schema: JSONSchema): JSONSchema { + if (!schema || typeof schema !== 'object') { + return schema; + } + + if (Array.isArray(schema)) { + return schema.map((item) => removeAnyOf(item)) as JSONSchema; + } + + const result = { ...schema }; + + if ('anyOf' in result && Array.isArray(result.anyOf) && result.anyOf.length > 0) { + const firstVariant = result.anyOf[0]; + + if (firstVariant && typeof firstVariant === 'object') { + // Special handling for properties to ensure deep merge + if (firstVariant.properties && result.properties) { + result.properties = { + ...result.properties, + ...(firstVariant.properties as Record), + }; + } else if (firstVariant.properties) { + result.properties = { ...firstVariant.properties }; + } + + for (const key in firstVariant) { + if (key !== 'properties') { + result[key] = firstVariant[key]; + } + } + } + + delete result.anyOf; + } + + for (const key in result) { + if (result[key] && typeof result[key] === 'object') { + result[key] = removeAnyOf(result[key] as JSONSchema); + } + } + + return result; +} + +/** + * Removes format fields from a schema and appends them to the description. + */ +export function removeFormats(schema: JSONSchema, formatsCapability: boolean): JSONSchema { + if (formatsCapability) { + return schema; + } + + if (!schema || typeof schema !== 'object') { + return schema; + } + + if (Array.isArray(schema)) { + return schema.map((item) => removeFormats(item, formatsCapability)) as JSONSchema; + } + + const result = { ...schema }; + + if ('format' in result && typeof result['format'] === 'string') { + const formatStr = `(format: "${result['format']}")`; + + if ('description' in result && typeof result['description'] === 'string') { + result['description'] = `${result['description']} ${formatStr}`; + } else { + result['description'] = formatStr; + } + + delete result['format']; + } + + for (const key in result) { + if (result[key] && typeof result[key] === 'object') { + result[key] = removeFormats(result[key] as JSONSchema, formatsCapability); + } + } + + return result; +} + +/** + * Applies all compatibility transformations to the endpoints based on the provided capabilities. + */ +export function applyCompatibilityTransformations( + endpoints: Endpoint[], + capabilities: ClientCapabilities, +): Endpoint[] { + let transformedEndpoints = [...endpoints]; + + // Handle top-level unions first as this changes tool names + if (!capabilities.topLevelUnions) { + const newEndpoints: Endpoint[] = []; + + for (const endpoint of transformedEndpoints) { + const variantTools = removeTopLevelUnions(endpoint.tool); + + if (variantTools.length === 1) { + newEndpoints.push(endpoint); + } else { + for (const variantTool of variantTools) { + newEndpoints.push({ + ...endpoint, + tool: variantTool, + }); + } + } + } + + transformedEndpoints = newEndpoints; + } + + if (capabilities.toolNameLength) { + const toolNames = transformedEndpoints.map((endpoint) => endpoint.tool.name); + const renameMap = truncateToolNames(toolNames, capabilities.toolNameLength); + + transformedEndpoints = transformedEndpoints.map((endpoint) => ({ + ...endpoint, + tool: { + ...endpoint.tool, + name: renameMap.get(endpoint.tool.name) ?? endpoint.tool.name, + }, + })); + } + + if (!capabilities.refs || !capabilities.unions || !capabilities.formats) { + transformedEndpoints = transformedEndpoints.map((endpoint) => { + let schema = endpoint.tool.inputSchema as JSONSchema; + + if (!capabilities.refs) { + schema = inlineRefs(schema); + } + + if (!capabilities.unions) { + schema = removeAnyOf(schema); + } + + if (!capabilities.formats) { + schema = removeFormats(schema, capabilities.formats); + } + + return { + ...endpoint, + tool: { + ...endpoint.tool, + inputSchema: schema as typeof endpoint.tool.inputSchema, + }, + }; + }); + } + + return transformedEndpoints; +} + +function toSnakeCase(str: string): string { + return str + .replace(/\s+/g, '_') + .replace(/([a-z])([A-Z])/g, '$1_$2') + .toLowerCase(); +} diff --git a/packages/mcp-server/src/docs-search-tool.ts b/packages/mcp-server/src/docs-search-tool.ts new file mode 100644 index 00000000..63e9a262 --- /dev/null +++ b/packages/mcp-server/src/docs-search-tool.ts @@ -0,0 +1,48 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from './tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; + +export const metadata: Metadata = { + resource: 'all', + operation: 'read', + tags: [], + httpMethod: 'get', +}; + +export const tool: Tool = { + name: 'search_docs', + description: + 'Search for documentation for how to use the client to interact with the API.\nThe tool will return an array of Markdown-formatted documentation pages.', + inputSchema: { + type: 'object', + properties: { + query: { + type: 'string', + description: 'The query to search for.', + }, + language: { + type: 'string', + description: 'The language for the SDK to search for.', + enum: ['http', 'python', 'go', 'typescript', 'terraform', 'ruby', 'java', 'kotlin'], + }, + }, + required: ['query', 'language'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +const docsSearchURL = + process.env['DOCS_SEARCH_URL'] || 'https://api.stainless.com/api/projects/terminal49/docs/search'; + +export const handler = async (_: unknown, args: Record | undefined) => { + const body = args as any; + const query = new URLSearchParams(body).toString(); + const result = await fetch(`${docsSearchURL}?${query}`); + return asTextContentResult(await result.json()); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/dynamic-tools.ts b/packages/mcp-server/src/dynamic-tools.ts new file mode 100644 index 00000000..6b61fec2 --- /dev/null +++ b/packages/mcp-server/src/dynamic-tools.ts @@ -0,0 +1,159 @@ +import Terminal49 from 'terminal49'; +import { Endpoint, asTextContentResult, ToolCallResult } from './tools/types'; +import { zodToJsonSchema } from 'zod-to-json-schema'; +import { z } from 'zod'; +import { Cabidela } from '@cloudflare/cabidela'; + +function zodToInputSchema(schema: z.ZodSchema) { + return { + type: 'object' as const, + ...(zodToJsonSchema(schema) as any), + }; +} + +/** + * A list of tools that expose all the endpoints in the API dynamically. + * + * Instead of exposing every endpoint as its own tool, which uses up too many tokens for LLMs to use at once, + * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then + * a generic endpoint that can be used to invoke any endpoint with the provided arguments. + * + * @param endpoints - The endpoints to include in the list. + */ +export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { + const listEndpointsSchema = z.object({ + search_query: z + .string() + .optional() + .describe( + 'An optional search query to filter the endpoints by. Provide a partial name, resource, operation, or tag to filter the endpoints returned.', + ), + }); + + const listEndpointsTool = { + metadata: { + resource: 'dynamic_tools', + operation: 'read' as const, + tags: [], + }, + tool: { + name: 'list_api_endpoints', + description: 'List or search for all endpoints in the Terminal49 TypeScript API', + inputSchema: zodToInputSchema(listEndpointsSchema), + }, + handler: async ( + client: Terminal49, + args: Record | undefined, + ): Promise => { + const query = args && listEndpointsSchema.parse(args).search_query?.trim(); + + const filteredEndpoints = + query && query.length > 0 ? + endpoints.filter((endpoint) => { + const fieldsToMatch = [ + endpoint.tool.name, + endpoint.tool.description, + endpoint.metadata.resource, + endpoint.metadata.operation, + ...endpoint.metadata.tags, + ]; + return fieldsToMatch.some((field) => field && field.toLowerCase().includes(query.toLowerCase())); + }) + : endpoints; + + return asTextContentResult({ + tools: filteredEndpoints.map(({ tool, metadata }) => ({ + name: tool.name, + description: tool.description, + resource: metadata.resource, + operation: metadata.operation, + tags: metadata.tags, + })), + }); + }, + }; + + const getEndpointSchema = z.object({ + endpoint: z.string().describe('The name of the endpoint to get the schema for.'), + }); + const getEndpointTool = { + metadata: { + resource: 'dynamic_tools', + operation: 'read' as const, + tags: [], + }, + tool: { + name: 'get_api_endpoint_schema', + description: + 'Get the schema for an endpoint in the Terminal49 TypeScript API. You can use the schema returned by this tool to invoke an endpoint with the `invoke_api_endpoint` tool.', + inputSchema: zodToInputSchema(getEndpointSchema), + }, + handler: async (client: Terminal49, args: Record | undefined) => { + if (!args) { + throw new Error('No endpoint provided'); + } + const endpointName = getEndpointSchema.parse(args).endpoint; + + const endpoint = endpoints.find((e) => e.tool.name === endpointName); + if (!endpoint) { + throw new Error(`Endpoint ${endpointName} not found`); + } + return asTextContentResult(endpoint.tool); + }, + }; + + const invokeEndpointSchema = z.object({ + endpoint_name: z.string().describe('The name of the endpoint to invoke.'), + args: z + .record(z.string(), z.any()) + .describe( + 'The arguments to pass to the endpoint. This must match the schema returned by the `get_api_endpoint_schema` tool.', + ), + }); + + const invokeEndpointTool = { + metadata: { + resource: 'dynamic_tools', + operation: 'write' as const, + tags: [], + }, + tool: { + name: 'invoke_api_endpoint', + description: + 'Invoke an endpoint in the Terminal49 TypeScript API. Note: use the `list_api_endpoints` tool to get the list of endpoints and `get_api_endpoint_schema` tool to get the schema for an endpoint.', + inputSchema: zodToInputSchema(invokeEndpointSchema), + }, + handler: async ( + client: Terminal49, + args: Record | undefined, + ): Promise => { + if (!args) { + throw new Error('No endpoint provided'); + } + const { success, data, error } = invokeEndpointSchema.safeParse(args); + if (!success) { + throw new Error(`Invalid arguments for endpoint. ${error?.format()}`); + } + const { endpoint_name, args: endpointArgs } = data; + + const endpoint = endpoints.find((e) => e.tool.name === endpoint_name); + if (!endpoint) { + throw new Error( + `Endpoint ${endpoint_name} not found. Use the \`list_api_endpoints\` tool to get the list of available endpoints.`, + ); + } + + try { + // Try to validate the arguments for a better error message + const cabidela = new Cabidela(endpoint.tool.inputSchema, { fullErrors: true }); + cabidela.validate(endpointArgs); + } catch (error) { + throw new Error(`Invalid arguments for endpoint ${endpoint_name}:\n${error}`); + } + + return await endpoint.handler(client, endpointArgs); + }, + }; + + return [getEndpointTool, listEndpointsTool, invokeEndpointTool]; +} diff --git a/packages/mcp-server/src/filtering.ts b/packages/mcp-server/src/filtering.ts new file mode 100644 index 00000000..eaae0fcf --- /dev/null +++ b/packages/mcp-server/src/filtering.ts @@ -0,0 +1,18 @@ +// @ts-nocheck +import initJq from 'jq-web'; + +export async function maybeFilter(jqFilter: unknown | undefined, response: any): Promise { + if (jqFilter && typeof jqFilter === 'string') { + return await jq(response, jqFilter); + } else { + return response; + } +} + +async function jq(json: any, jqFilter: string) { + return (await initJq).json(json, jqFilter); +} + +export function isJqError(error: any): error is Error { + return error instanceof Error && 'stderr' in error; +} diff --git a/packages/mcp-server/src/headers.ts b/packages/mcp-server/src/headers.ts new file mode 100644 index 00000000..2a750764 --- /dev/null +++ b/packages/mcp-server/src/headers.ts @@ -0,0 +1,12 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { IncomingMessage } from 'node:http'; +import { ClientOptions } from 'terminal49'; + +export const parseAuthHeaders = (req: IncomingMessage): Partial => { + const apiKey = + Array.isArray(req.headers['x-terminal49-api-key']) ? + req.headers['x-terminal49-api-key'][0] + : req.headers['x-terminal49-api-key']; + return { apiKey }; +}; diff --git a/packages/mcp-server/src/http.ts b/packages/mcp-server/src/http.ts new file mode 100644 index 00000000..84517003 --- /dev/null +++ b/packages/mcp-server/src/http.ts @@ -0,0 +1,127 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; + +import express from 'express'; +import { fromError } from 'zod-validation-error/v3'; +import { McpOptions, parseQueryOptions } from './options'; +import { ClientOptions, initMcpServer, newMcpServer } from './server'; +import { parseAuthHeaders } from './headers'; + +const newServer = ({ + clientOptions, + mcpOptions: defaultMcpOptions, + req, + res, +}: { + clientOptions: ClientOptions; + mcpOptions: McpOptions; + req: express.Request; + res: express.Response; +}): McpServer | null => { + const server = newMcpServer(); + + let mcpOptions: McpOptions; + try { + mcpOptions = parseQueryOptions(defaultMcpOptions, req.query); + } catch (error) { + res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: `Invalid request: ${fromError(error)}`, + }, + }); + return null; + } + + try { + const authOptions = parseAuthHeaders(req); + initMcpServer({ + server: server, + clientOptions: { + ...clientOptions, + ...authOptions, + }, + mcpOptions, + }); + } catch (error) { + res.status(401).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: `Unauthorized: ${error instanceof Error ? error.message : error}`, + }, + }); + return null; + } + + return server; +}; + +const post = + (options: { clientOptions: ClientOptions; mcpOptions: McpOptions }) => + async (req: express.Request, res: express.Response) => { + const server = newServer({ ...options, req, res }); + // If we return null, we already set the authorization error. + if (server === null) return; + const transport = new StreamableHTTPServerTransport({ + // Stateless server + sessionIdGenerator: undefined, + }); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); + }; + +const get = async (req: express.Request, res: express.Response) => { + res.status(405).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Method not supported', + }, + }); +}; + +const del = async (req: express.Request, res: express.Response) => { + res.status(405).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Method not supported', + }, + }); +}; + +export const streamableHTTPApp = ({ + clientOptions = {}, + mcpOptions = {}, +}: { + clientOptions?: ClientOptions; + mcpOptions?: McpOptions; +}): express.Express => { + const app = express(); + app.set('query parser', 'extended'); + app.use(express.json()); + + app.get('/', get); + app.post('/', post({ clientOptions, mcpOptions })); + app.delete('/', del); + + return app; +}; + +export const launchStreamableHTTPServer = async (options: McpOptions, port: number | string | undefined) => { + const app = streamableHTTPApp({ mcpOptions: options }); + const server = app.listen(port); + const address = server.address(); + + if (typeof address === 'string') { + console.error(`MCP Server running on streamable HTTP at ${address}`); + } else if (address !== null) { + console.error(`MCP Server running on streamable HTTP on port ${address.port}`); + } else { + console.error(`MCP Server running on streamable HTTP on port ${port}`); + } +}; diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts new file mode 100644 index 00000000..4850a0e2 --- /dev/null +++ b/packages/mcp-server/src/index.ts @@ -0,0 +1,108 @@ +#!/usr/bin/env node + +import { selectTools } from './server'; +import { Endpoint, endpoints } from './tools'; +import { McpOptions, parseCLIOptions } from './options'; +import { launchStdioServer } from './stdio'; +import { launchStreamableHTTPServer } from './http'; + +async function main() { + const options = parseOptionsOrError(); + + if (options.list) { + listAllTools(); + return; + } + + const selectedTools = await selectToolsOrError(endpoints, options); + + console.error( + `MCP Server starting with ${selectedTools.length} tools:`, + selectedTools.map((e) => e.tool.name), + ); + + switch (options.transport) { + case 'stdio': + await launchStdioServer(options); + break; + case 'http': + await launchStreamableHTTPServer(options, options.port ?? options.socket); + break; + } +} + +if (require.main === module) { + main().catch((error) => { + console.error('Fatal error in main():', error); + process.exit(1); + }); +} + +function parseOptionsOrError() { + try { + return parseCLIOptions(); + } catch (error) { + console.error('Error parsing options:', error); + process.exit(1); + } +} + +async function selectToolsOrError(endpoints: Endpoint[], options: McpOptions): Promise { + try { + const includedTools = await selectTools(endpoints, options); + if (includedTools.length === 0) { + console.error('No tools match the provided filters.'); + process.exit(1); + } + return includedTools; + } catch (error) { + if (error instanceof Error) { + console.error('Error filtering tools:', error.message); + } else { + console.error('Error filtering tools:', error); + } + process.exit(1); + } +} + +function listAllTools() { + if (endpoints.length === 0) { + console.log('No tools available.'); + return; + } + console.log('Available tools:\n'); + + // Group endpoints by resource + const resourceGroups = new Map(); + + for (const endpoint of endpoints) { + const resource = endpoint.metadata.resource; + if (!resourceGroups.has(resource)) { + resourceGroups.set(resource, []); + } + resourceGroups.get(resource)!.push(endpoint); + } + + // Sort resources alphabetically + const sortedResources = Array.from(resourceGroups.keys()).sort(); + + // Display hierarchically by resource + for (const resource of sortedResources) { + console.log(`Resource: ${resource}`); + + const resourceEndpoints = resourceGroups.get(resource)!; + // Sort endpoints by tool name + resourceEndpoints.sort((a, b) => a.tool.name.localeCompare(b.tool.name)); + + for (const endpoint of resourceEndpoints) { + const { + tool, + metadata: { operation, tags }, + } = endpoint; + + console.log(` - ${tool.name} (${operation}) ${tags.length > 0 ? `tags: ${tags.join(', ')}` : ''}`); + console.log(` Description: ${tool.description}`); + } + console.log(''); + } +} diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts new file mode 100644 index 00000000..b6ff5976 --- /dev/null +++ b/packages/mcp-server/src/options.ts @@ -0,0 +1,473 @@ +import qs from 'qs'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import z from 'zod'; +import { endpoints, Filter } from './tools'; +import { ClientCapabilities, knownClients, ClientType } from './compat'; + +export type CLIOptions = McpOptions & { + list: boolean; + transport: 'stdio' | 'http'; + port: number | undefined; + socket: string | undefined; +}; + +export type McpOptions = { + client?: ClientType | undefined; + includeDynamicTools?: boolean | undefined; + includeAllTools?: boolean | undefined; + includeCodeTools?: boolean | undefined; + includeDocsTools?: boolean | undefined; + filters?: Filter[] | undefined; + capabilities?: Partial | undefined; +}; + +const CAPABILITY_CHOICES = [ + 'top-level-unions', + 'valid-json', + 'refs', + 'unions', + 'formats', + 'tool-name-length', +] as const; + +type Capability = (typeof CAPABILITY_CHOICES)[number]; + +function parseCapabilityValue(cap: string): { name: Capability; value?: number } { + if (cap.startsWith('tool-name-length=')) { + const parts = cap.split('='); + if (parts.length === 2) { + const length = parseInt(parts[1]!, 10); + if (!isNaN(length)) { + return { name: 'tool-name-length', value: length }; + } + throw new Error(`Invalid tool-name-length value: ${parts[1]}. Expected a number.`); + } + throw new Error(`Invalid format for tool-name-length. Expected tool-name-length=N.`); + } + if (!CAPABILITY_CHOICES.includes(cap as Capability)) { + throw new Error(`Unknown capability: ${cap}. Valid capabilities are: ${CAPABILITY_CHOICES.join(', ')}`); + } + return { name: cap as Capability }; +} + +export function parseCLIOptions(): CLIOptions { + const opts = yargs(hideBin(process.argv)) + .option('tools', { + type: 'string', + array: true, + choices: ['dynamic', 'all', 'code', 'docs'], + description: 'Use dynamic tools or all tools', + }) + .option('no-tools', { + type: 'string', + array: true, + choices: ['dynamic', 'all', 'code', 'docs'], + description: 'Do not use any dynamic or all tools', + }) + .option('tool', { + type: 'string', + array: true, + description: 'Include tools matching the specified names', + }) + .option('resource', { + type: 'string', + array: true, + description: 'Include tools matching the specified resources', + }) + .option('operation', { + type: 'string', + array: true, + choices: ['read', 'write'], + description: 'Include tools matching the specified operations', + }) + .option('tag', { + type: 'string', + array: true, + description: 'Include tools with the specified tags', + }) + .option('no-tool', { + type: 'string', + array: true, + description: 'Exclude tools matching the specified names', + }) + .option('no-resource', { + type: 'string', + array: true, + description: 'Exclude tools matching the specified resources', + }) + .option('no-operation', { + type: 'string', + array: true, + description: 'Exclude tools matching the specified operations', + }) + .option('no-tag', { + type: 'string', + array: true, + description: 'Exclude tools with the specified tags', + }) + .option('list', { + type: 'boolean', + description: 'List all tools and exit', + }) + .option('client', { + type: 'string', + choices: Object.keys(knownClients), + description: 'Specify the MCP client being used', + }) + .option('capability', { + type: 'string', + array: true, + description: 'Specify client capabilities', + coerce: (values: string[]) => { + return values.flatMap((v) => v.split(',')); + }, + }) + .option('no-capability', { + type: 'string', + array: true, + description: 'Unset client capabilities', + choices: CAPABILITY_CHOICES, + coerce: (values: string[]) => { + return values.flatMap((v) => v.split(',')); + }, + }) + .option('describe-capabilities', { + type: 'boolean', + description: 'Print detailed explanation of client capabilities and exit', + }) + .option('transport', { + type: 'string', + choices: ['stdio', 'http'], + default: 'stdio', + description: 'What transport to use; stdio for local servers or http for remote servers', + }) + .option('port', { + type: 'number', + description: 'Port to serve on if using http transport', + }) + .option('socket', { + type: 'string', + description: 'Unix socket to serve on if using http transport', + }) + .help(); + + for (const [command, desc] of examples()) { + opts.example(command, desc); + } + + const argv = opts.parseSync(); + + // Handle describe-capabilities flag + if (argv.describeCapabilities) { + console.log(getCapabilitiesExplanation()); + process.exit(0); + } + + const filters: Filter[] = []; + + // Helper function to support comma-separated values + const splitValues = (values: string[] | undefined): string[] => { + if (!values) return []; + return values.flatMap((v) => v.split(',')); + }; + + for (const tag of splitValues(argv.tag)) { + filters.push({ type: 'tag', op: 'include', value: tag }); + } + + for (const tag of splitValues(argv.noTag)) { + filters.push({ type: 'tag', op: 'exclude', value: tag }); + } + + for (const resource of splitValues(argv.resource)) { + filters.push({ type: 'resource', op: 'include', value: resource }); + } + + for (const resource of splitValues(argv.noResource)) { + filters.push({ type: 'resource', op: 'exclude', value: resource }); + } + + for (const tool of splitValues(argv.tool)) { + filters.push({ type: 'tool', op: 'include', value: tool }); + } + + for (const tool of splitValues(argv.noTool)) { + filters.push({ type: 'tool', op: 'exclude', value: tool }); + } + + for (const operation of splitValues(argv.operation)) { + filters.push({ type: 'operation', op: 'include', value: operation }); + } + + for (const operation of splitValues(argv.noOperation)) { + filters.push({ type: 'operation', op: 'exclude', value: operation }); + } + + // Parse client capabilities + const clientCapabilities: Partial = {}; + + // Apply individual capability overrides + if (Array.isArray(argv.capability)) { + for (const cap of argv.capability) { + const parsedCap = parseCapabilityValue(cap); + if (parsedCap.name === 'top-level-unions') { + clientCapabilities.topLevelUnions = true; + } else if (parsedCap.name === 'valid-json') { + clientCapabilities.validJson = true; + } else if (parsedCap.name === 'refs') { + clientCapabilities.refs = true; + } else if (parsedCap.name === 'unions') { + clientCapabilities.unions = true; + } else if (parsedCap.name === 'formats') { + clientCapabilities.formats = true; + } else if (parsedCap.name === 'tool-name-length') { + clientCapabilities.toolNameLength = parsedCap.value; + } + } + } + + // Handle no-capability options to unset capabilities + if (Array.isArray(argv.noCapability)) { + for (const cap of argv.noCapability) { + if (cap === 'top-level-unions') { + clientCapabilities.topLevelUnions = false; + } else if (cap === 'valid-json') { + clientCapabilities.validJson = false; + } else if (cap === 'refs') { + clientCapabilities.refs = false; + } else if (cap === 'unions') { + clientCapabilities.unions = false; + } else if (cap === 'formats') { + clientCapabilities.formats = false; + } else if (cap === 'tool-name-length') { + clientCapabilities.toolNameLength = undefined; + } + } + } + + const shouldIncludeToolType = (toolType: 'dynamic' | 'all' | 'code' | 'docs') => + argv.noTools?.includes(toolType) ? false + : argv.tools?.includes(toolType) ? true + : undefined; + + const includeDynamicTools = shouldIncludeToolType('dynamic'); + const includeAllTools = shouldIncludeToolType('all'); + const includeCodeTools = shouldIncludeToolType('code'); + const includeDocsTools = shouldIncludeToolType('docs'); + + const transport = argv.transport as 'stdio' | 'http'; + + const client = argv.client as ClientType; + return { + client: client && client !== 'infer' && knownClients[client] ? client : undefined, + includeDynamicTools, + includeAllTools, + includeCodeTools, + includeDocsTools, + filters, + capabilities: clientCapabilities, + list: argv.list || false, + transport, + port: argv.port, + socket: argv.socket, + }; +} + +const coerceArray = (zodType: T) => + z.preprocess( + (val) => + Array.isArray(val) ? val + : val ? [val] + : val, + z.array(zodType).optional(), + ); + +const QueryOptions = z.object({ + tools: coerceArray(z.enum(['dynamic', 'all', 'code', 'docs'])).describe('Specify which MCP tools to use'), + no_tools: coerceArray(z.enum(['dynamic', 'all', 'code', 'docs'])).describe( + 'Specify which MCP tools to not use.', + ), + tool: coerceArray(z.string()).describe('Include tools matching the specified names'), + resource: coerceArray(z.string()).describe('Include tools matching the specified resources'), + operation: coerceArray(z.enum(['read', 'write'])).describe( + 'Include tools matching the specified operations', + ), + tag: coerceArray(z.string()).describe('Include tools with the specified tags'), + no_tool: coerceArray(z.string()).describe('Exclude tools matching the specified names'), + no_resource: coerceArray(z.string()).describe('Exclude tools matching the specified resources'), + no_operation: coerceArray(z.enum(['read', 'write'])).describe( + 'Exclude tools matching the specified operations', + ), + no_tag: coerceArray(z.string()).describe('Exclude tools with the specified tags'), + client: ClientType.optional().describe('Specify the MCP client being used'), + capability: coerceArray(z.string()).describe('Specify client capabilities'), + no_capability: coerceArray(z.enum(CAPABILITY_CHOICES)).describe('Unset client capabilities'), +}); + +export function parseQueryOptions(defaultOptions: McpOptions, query: unknown): McpOptions { + const queryObject = typeof query === 'string' ? qs.parse(query) : query; + const queryOptions = QueryOptions.parse(queryObject); + + const filters: Filter[] = [...(defaultOptions.filters ?? [])]; + + for (const resource of queryOptions.resource || []) { + filters.push({ type: 'resource', op: 'include', value: resource }); + } + for (const operation of queryOptions.operation || []) { + filters.push({ type: 'operation', op: 'include', value: operation }); + } + for (const tag of queryOptions.tag || []) { + filters.push({ type: 'tag', op: 'include', value: tag }); + } + for (const tool of queryOptions.tool || []) { + filters.push({ type: 'tool', op: 'include', value: tool }); + } + for (const resource of queryOptions.no_resource || []) { + filters.push({ type: 'resource', op: 'exclude', value: resource }); + } + for (const operation of queryOptions.no_operation || []) { + filters.push({ type: 'operation', op: 'exclude', value: operation }); + } + for (const tag of queryOptions.no_tag || []) { + filters.push({ type: 'tag', op: 'exclude', value: tag }); + } + for (const tool of queryOptions.no_tool || []) { + filters.push({ type: 'tool', op: 'exclude', value: tool }); + } + + // Parse client capabilities + const clientCapabilities: Partial = { ...defaultOptions.capabilities }; + + for (const cap of queryOptions.capability || []) { + const parsed = parseCapabilityValue(cap); + if (parsed.name === 'top-level-unions') { + clientCapabilities.topLevelUnions = true; + } else if (parsed.name === 'valid-json') { + clientCapabilities.validJson = true; + } else if (parsed.name === 'refs') { + clientCapabilities.refs = true; + } else if (parsed.name === 'unions') { + clientCapabilities.unions = true; + } else if (parsed.name === 'formats') { + clientCapabilities.formats = true; + } else if (parsed.name === 'tool-name-length') { + clientCapabilities.toolNameLength = parsed.value; + } + } + + for (const cap of queryOptions.no_capability || []) { + if (cap === 'top-level-unions') { + clientCapabilities.topLevelUnions = false; + } else if (cap === 'valid-json') { + clientCapabilities.validJson = false; + } else if (cap === 'refs') { + clientCapabilities.refs = false; + } else if (cap === 'unions') { + clientCapabilities.unions = false; + } else if (cap === 'formats') { + clientCapabilities.formats = false; + } else if (cap === 'tool-name-length') { + clientCapabilities.toolNameLength = undefined; + } + } + + let dynamicTools: boolean | undefined = + queryOptions.no_tools && queryOptions.no_tools?.includes('dynamic') ? false + : queryOptions.tools?.includes('dynamic') ? true + : defaultOptions.includeDynamicTools; + + let allTools: boolean | undefined = + queryOptions.no_tools && queryOptions.no_tools?.includes('all') ? false + : queryOptions.tools?.includes('all') ? true + : defaultOptions.includeAllTools; + + let docsTools: boolean | undefined = + queryOptions.no_tools && queryOptions.no_tools?.includes('docs') ? false + : queryOptions.tools?.includes('docs') ? true + : defaultOptions.includeDocsTools; + + let codeTools: boolean | undefined = + queryOptions.no_tools && queryOptions.no_tools?.includes('code') ? false + : queryOptions.tools?.includes('code') && defaultOptions.includeCodeTools ? true + : defaultOptions.includeCodeTools; + + return { + client: queryOptions.client ?? defaultOptions.client, + includeDynamicTools: dynamicTools, + includeAllTools: allTools, + includeCodeTools: codeTools, + includeDocsTools: docsTools, + filters, + capabilities: clientCapabilities, + }; +} + +function getCapabilitiesExplanation(): string { + return ` +Client Capabilities Explanation: + +Different Language Models (LLMs) and the MCP clients that use them have varying limitations in how they handle tool schemas. Capability flags allow you to inform the MCP server about these limitations. + +When a capability flag is set to false, the MCP server will automatically adjust the tool schemas to work around that limitation, ensuring broader compatibility. + +Available Capabilities: + +# top-level-unions +Some clients/LLMs do not support JSON schemas with a union type (anyOf) at the root level. If a client lacks this capability, the MCP server splits tools with top-level unions into multiple separate tools, one for each variant in the union. + +# refs +Some clients/LLMs do not support $ref pointers for schema reuse. If a client lacks this capability, the MCP server automatically inlines all references ($defs) directly into the schema. Properties that would cause circular references are removed during this process. + +# valid-json +Some clients/LLMs may incorrectly send arguments as a JSON-encoded string instead of a proper JSON object. If a client *has* this capability, the MCP server will attempt to parse string values as JSON if the initial validation against the schema fails. + +# unions +Some clients/LLMs do not support union types (anyOf) in JSON schemas. If a client lacks this capability, the MCP server removes all anyOf fields and uses only the first variant as the schema. + +# formats +Some clients/LLMs do not support the 'format' keyword in JSON Schema specifications. If a client lacks this capability, the MCP server removes all format fields and appends the format information to the field's description in parentheses. + +# tool-name-length=N +Some clients/LLMs impose a maximum length on tool names. If this capability is set, the MCP server will automatically truncate tool names exceeding the specified length (N), ensuring uniqueness by appending numbers if necessary. + +Client Presets (--client): +Presets like '--client=openai-agents' or '--client=cursor' automatically configure these capabilities based on current known limitations of those clients, simplifying setup. + +Current presets: +${JSON.stringify(knownClients, null, 2)} + `; +} + +function examples(): [string, string][] { + const firstEndpoint = endpoints[0]!; + const secondEndpoint = + endpoints.find((e) => e.metadata.resource !== firstEndpoint.metadata.resource) || endpoints[1]; + const tag = endpoints.find((e) => e.metadata.tags.length > 0)?.metadata.tags[0]; + const otherEndpoint = secondEndpoint || firstEndpoint; + + return [ + [ + `--tool="${firstEndpoint.tool.name}" ${secondEndpoint ? `--tool="${secondEndpoint.tool.name}"` : ''}`, + 'Include tools by name', + ], + [ + `--resource="${firstEndpoint.metadata.resource}" --operation="read"`, + 'Filter by resource and operation', + ], + [ + `--resource="${otherEndpoint.metadata.resource}*" --no-tool="${otherEndpoint.tool.name}"`, + 'Use resource wildcards and exclusions', + ], + [`--client="cursor"`, 'Adjust schemas to be more compatible with Cursor'], + [ + `--capability="top-level-unions" --capability="tool-name-length=40"`, + 'Specify individual client capabilities', + ], + [ + `--client="cursor" --no-capability="tool-name-length"`, + 'Use cursor client preset but remove tool name length limit', + ], + ...(tag ? [[`--tag="${tag}"`, 'Filter based on tags'] as [string, string]] : []), + ]; +} diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts new file mode 100644 index 00000000..aa70f45b --- /dev/null +++ b/packages/mcp-server/src/server.ts @@ -0,0 +1,207 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { Endpoint, endpoints, HandlerFunction, query } from './tools'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, + SetLevelRequestSchema, + Implementation, + Tool, +} from '@modelcontextprotocol/sdk/types.js'; +import { ClientOptions } from 'terminal49'; +import Terminal49 from 'terminal49'; +import { + applyCompatibilityTransformations, + ClientCapabilities, + defaultClientCapabilities, + knownClients, + parseEmbeddedJSON, +} from './compat'; +import { dynamicTools } from './dynamic-tools'; +import { codeTool } from './code-tool'; +import docsSearchTool from './docs-search-tool'; +import { McpOptions } from './options'; + +export { McpOptions } from './options'; +export { ClientType } from './compat'; +export { Filter } from './tools'; +export { ClientOptions } from 'terminal49'; +export { endpoints } from './tools'; + +export const newMcpServer = () => + new McpServer( + { + name: 'terminal49_api', + version: '0.1.0-alpha.1', + }, + { capabilities: { tools: {}, logging: {} } }, + ); + +// Create server instance +export const server = newMcpServer(); + +/** + * Initializes the provided MCP Server with the given tools and handlers. + * If not provided, the default client, tools and handlers will be used. + */ +export function initMcpServer(params: { + server: Server | McpServer; + clientOptions?: ClientOptions; + mcpOptions?: McpOptions; +}) { + const server = params.server instanceof McpServer ? params.server.server : params.server; + const mcpOptions = params.mcpOptions ?? {}; + + let providedEndpoints: Endpoint[] | null = null; + let endpointMap: Record | null = null; + + const initTools = async (implementation?: Implementation) => { + if (implementation && (!mcpOptions.client || mcpOptions.client === 'infer')) { + mcpOptions.client = + implementation.name.toLowerCase().includes('claude') ? 'claude' + : implementation.name.toLowerCase().includes('cursor') ? 'cursor' + : undefined; + mcpOptions.capabilities = { + ...(mcpOptions.client && knownClients[mcpOptions.client]), + ...mcpOptions.capabilities, + }; + } + providedEndpoints ??= await selectTools(endpoints, mcpOptions); + endpointMap ??= Object.fromEntries(providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint])); + }; + + const logAtLevel = + (level: 'debug' | 'info' | 'warning' | 'error') => + (message: string, ...rest: unknown[]) => { + void server.sendLoggingMessage({ + level, + data: { message, rest }, + }); + }; + const logger = { + debug: logAtLevel('debug'), + info: logAtLevel('info'), + warn: logAtLevel('warning'), + error: logAtLevel('error'), + }; + + let client = new Terminal49({ + logger, + ...params.clientOptions, + defaultHeaders: { + ...params.clientOptions?.defaultHeaders, + 'X-Stainless-MCP': 'true', + }, + }); + + server.setRequestHandler(ListToolsRequestSchema, async () => { + if (providedEndpoints === null) { + await initTools(server.getClientVersion()); + } + return { + tools: providedEndpoints!.map((endpoint) => endpoint.tool), + }; + }); + + server.setRequestHandler(CallToolRequestSchema, async (request) => { + if (endpointMap === null) { + await initTools(server.getClientVersion()); + } + const { name, arguments: args } = request.params; + const endpoint = endpointMap![name]; + if (!endpoint) { + throw new Error(`Unknown tool: ${name}`); + } + + return executeHandler(endpoint.tool, endpoint.handler, client, args, mcpOptions.capabilities); + }); + + server.setRequestHandler(SetLevelRequestSchema, async (request) => { + const { level } = request.params; + switch (level) { + case 'debug': + client = client.withOptions({ logLevel: 'debug' }); + break; + case 'info': + client = client.withOptions({ logLevel: 'info' }); + break; + case 'notice': + case 'warning': + client = client.withOptions({ logLevel: 'warn' }); + break; + case 'error': + client = client.withOptions({ logLevel: 'error' }); + break; + default: + client = client.withOptions({ logLevel: 'off' }); + break; + } + return {}; + }); +} + +/** + * Selects the tools to include in the MCP Server based on the provided options. + */ +export async function selectTools(endpoints: Endpoint[], options?: McpOptions): Promise { + const filteredEndpoints = query(options?.filters ?? [], endpoints); + + let includedTools = filteredEndpoints.slice(); + + if (includedTools.length > 0) { + if (options?.includeDynamicTools) { + includedTools = dynamicTools(includedTools); + } + } else { + if (options?.includeAllTools) { + includedTools = endpoints.slice(); + } else if (options?.includeDynamicTools) { + includedTools = dynamicTools(endpoints); + } else if (options?.includeCodeTools) { + includedTools = [await codeTool()]; + } else { + includedTools = endpoints.slice(); + } + } + if (options?.includeDocsTools ?? true) { + includedTools.push(docsSearchTool); + } + const capabilities = { ...defaultClientCapabilities, ...options?.capabilities }; + return applyCompatibilityTransformations(includedTools, capabilities); +} + +/** + * Runs the provided handler with the given client and arguments. + */ +export async function executeHandler( + tool: Tool, + handler: HandlerFunction, + client: Terminal49, + args: Record | undefined, + compatibilityOptions?: Partial, +) { + const options = { ...defaultClientCapabilities, ...compatibilityOptions }; + if (!options.validJson && args) { + args = parseEmbeddedJSON(args, tool.inputSchema); + } + return await handler(client, args || {}); +} + +export const readEnv = (env: string): string | undefined => { + if (typeof (globalThis as any).process !== 'undefined') { + return (globalThis as any).process.env?.[env]?.trim(); + } else if (typeof (globalThis as any).Deno !== 'undefined') { + return (globalThis as any).Deno.env?.get?.(env)?.trim(); + } + return; +}; + +export const readEnvOrError = (env: string): string => { + let envValue = readEnv(env); + if (envValue === undefined) { + throw new Error(`Environment variable ${env} is not set`); + } + return envValue; +}; diff --git a/packages/mcp-server/src/stdio.ts b/packages/mcp-server/src/stdio.ts new file mode 100644 index 00000000..d902a5bb --- /dev/null +++ b/packages/mcp-server/src/stdio.ts @@ -0,0 +1,13 @@ +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { initMcpServer, newMcpServer } from './server'; +import { McpOptions } from './options'; + +export const launchStdioServer = async (options: McpOptions) => { + const server = newMcpServer(); + + initMcpServer({ server, mcpOptions: options }); + + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('MCP Server running on stdio'); +}; diff --git a/packages/mcp-server/src/tools.ts b/packages/mcp-server/src/tools.ts new file mode 100644 index 00000000..7e516de7 --- /dev/null +++ b/packages/mcp-server/src/tools.ts @@ -0,0 +1 @@ +export * from './tools/index'; diff --git a/packages/mcp-server/src/tools/containers/get-raw-events-containers.ts b/packages/mcp-server/src/tools/containers/get-raw-events-containers.ts new file mode 100644 index 00000000..38f7c46d --- /dev/null +++ b/packages/mcp-server/src/tools/containers/get-raw-events-containers.ts @@ -0,0 +1,54 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'containers', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/containers/{id}/raw_events', + operationId: 'get-containers-id-raw_events', +}; + +export const tool: Tool = { + name: 'get_raw_events_containers', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n#### Deprecation warning\nThe `raw_events` endpoint is provided as-is.\n\n For past events we recommend consuming `transport_events`.\n\n---\nGet a list of past and future (estimated) milestones for a container as reported by the carrier. Some of the data is normalized even though the API is called raw_events. \n\nNormalized attributes: `event` and `timestamp` timestamp. Not all of the `event` values have been normalized. You can expect the the events related to container movements to be normalized but there are cases where events are not normalized. \n\nFor past historical events we recommend consuming `transport_events`. Although there are fewer events here those events go through additional vetting and normalization to avoid false positives and get you correct data.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/container_get_raw_events_response',\n $defs: {\n container_get_raw_events_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n type: 'object',\n title: 'Raw Event Model',\n description: 'Raw Events represent the milestones from the shipping line for a given container.\\n\\n### About raw_event datetimes\\n\\nThe events may include estimated future events. The event is a future event if an `estimated_` timestamp is not null. \\n\\nThe datetime properties `timestamp` and `estimated`. \\n\\nWhen the `time_zone` property is present the datetimes are UTC timestamps, which can be converted to the local time by parsing the provided `time_zone`.\\n\\nWhen the `time_zone` property is absent, the datetimes represent local times which serialized as UTC timestamps for consistency. ',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n actual_at: {\n type: 'string',\n description: 'Deprecated: The datetime the event transpired in UTC',\n format: 'date-time'\n },\n actual_on: {\n type: 'string',\n description: 'Deprecated: The date of the event at the event location when no time information is available. ',\n format: 'date'\n },\n created_at: {\n type: 'string',\n description: 'When the raw_event was created in UTC',\n format: 'date-time'\n },\n estimated: {\n type: 'boolean',\n description: 'True if the timestamp is estimated, false otherwise'\n },\n estimated_at: {\n type: 'string',\n description: 'Deprecated: The estimated datetime the event will occur in UTC',\n format: 'date-time'\n },\n estimated_on: {\n type: 'string',\n description: 'Deprecated: The estimated date of the event at the event location when no time information is available. ',\n format: 'date'\n },\n event: {\n type: 'string',\n description: 'Normalized string representing the event',\n enum: [ 'empty_out',\n 'full_in',\n 'positioned_in',\n 'positioned_out',\n 'vessel_loaded',\n 'vessel_departed',\n 'transshipment_arrived',\n 'transshipment_discharged',\n 'transshipment_loaded',\n 'transshipment_departed',\n 'feeder_arrived',\n 'feeder_discharged',\n 'feeder_loaded',\n 'feeder_departed',\n 'rail_loaded',\n 'rail_departed',\n 'rail_arrived',\n 'rail_unloaded',\n 'vessel_arrived',\n 'vessel_discharged',\n 'arrived_at_destination',\n 'delivered',\n 'full_out',\n 'empty_in',\n 'vgm_received',\n 'carrier_release',\n 'customs_release'\n ]\n },\n index: {\n type: 'integer',\n description: 'The order of the event. This may be helpful when only dates (i.e. actual_on) are available.'\n },\n location_locode: {\n type: 'string',\n description: 'UNLOCODE of the event location'\n },\n location_name: {\n type: 'string',\n description: 'The city or facility name of the event location'\n },\n original_event: {\n type: 'string',\n description: 'The event name as returned by the carrier'\n },\n timestamp: {\n type: 'string',\n description: 'The datetime the event either transpired or will occur in UTC',\n format: 'date-time'\n },\n timezone: {\n type: 'string',\n description: 'IANA tz where the event occured'\n },\n vessel_imo: {\n type: 'string',\n description: 'The IMO of the vessel where applicable'\n },\n vessel_name: {\n type: 'string',\n description: 'The name of the vessel where applicable'\n },\n voyage_number: {\n type: 'string'\n }\n }\n },\n relationships: {\n type: 'object',\n properties: {\n location: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'port',\n 'metro_area'\n ]\n }\n }\n }\n }\n },\n vessel: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'vessel'\n ]\n }\n }\n }\n }\n }\n }\n },\n type: {\n type: 'string',\n enum: [ 'raw_event'\n ]\n }\n }\n }\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.containers.getRawEvents(id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/containers/get-transport-events-containers.ts b/packages/mcp-server/src/tools/containers/get-transport-events-containers.ts new file mode 100644 index 00000000..c72c0d63 --- /dev/null +++ b/packages/mcp-server/src/tools/containers/get-transport-events-containers.ts @@ -0,0 +1,44 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'containers', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/containers/{id}/transport_events', + operationId: 'get-containers-id-transport_events', +}; + +export const tool: Tool = { + name: 'get_transport_events_containers', + description: + 'Get a list of past transport events (canonical) for a container. All data has been normalized across all carriers. These are a verified subset of the raw events may also be sent as Webhook Notifications to a webhook endpoint.\n\nThis does not provide any estimated future events. See `container/:id/raw_events` endpoint for that. ', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + include: { + type: 'string', + description: 'Comma delimited list of relations to include', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, ...body } = args as any; + return asTextContentResult(await client.containers.getTransportEvents(id, body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/containers/list-containers.ts b/packages/mcp-server/src/tools/containers/list-containers.ts new file mode 100644 index 00000000..62c08ffc --- /dev/null +++ b/packages/mcp-server/src/tools/containers/list-containers.ts @@ -0,0 +1,51 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'containers', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/containers', + operationId: 'get-containers', +}; + +export const tool: Tool = { + name: 'list_containers', + description: + 'Returns a list of container. The containers are returned sorted by creation date, with the most recently refreshed containers appearing first.\n\nThis API will return all containers associated with the account.', + inputSchema: { + type: 'object', + properties: { + include: { + type: 'string', + description: 'Comma delimited list of relations to include', + }, + 'page[number]': { + type: 'integer', + }, + 'page[size]': { + type: 'integer', + }, + terminal_checked_before: { + type: 'integer', + description: 'Number of seconds in which containers were refreshed', + }, + }, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const body = args as any; + return asTextContentResult(await client.containers.list(body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/containers/retrieve-containers.ts b/packages/mcp-server/src/tools/containers/retrieve-containers.ts new file mode 100644 index 00000000..f8103f0a --- /dev/null +++ b/packages/mcp-server/src/tools/containers/retrieve-containers.ts @@ -0,0 +1,43 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'containers', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/containers/{id}', + operationId: 'get-containers-id', +}; + +export const tool: Tool = { + name: 'retrieve_containers', + description: 'Retrieves the details of a container.', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + include: { + type: 'string', + description: 'Comma delimited list of relations to include', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, ...body } = args as any; + return asTextContentResult(await client.containers.retrieve(id, body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/containers/update-containers.ts b/packages/mcp-server/src/tools/containers/update-containers.ts new file mode 100644 index 00000000..e9ad1a48 --- /dev/null +++ b/packages/mcp-server/src/tools/containers/update-containers.ts @@ -0,0 +1,51 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'containers', + operation: 'write', + tags: [], + httpMethod: 'patch', + httpPath: '/containers', + operationId: 'patch-containers-id', +}; + +export const tool: Tool = { + name: 'update_containers', + description: 'Update a container', + inputSchema: { + type: 'object', + properties: { + data: { + type: 'object', + properties: { + attributes: { + type: 'object', + properties: { + ref_numbers: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + }, + }, + required: ['attributes'], + }, + }, + required: [], + }, + annotations: {}, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const body = args as any; + return asTextContentResult(await client.containers.update(body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/index.ts b/packages/mcp-server/src/tools/index.ts new file mode 100644 index 00000000..2722d473 --- /dev/null +++ b/packages/mcp-server/src/tools/index.ts @@ -0,0 +1,137 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, Endpoint, HandlerFunction } from './types'; + +export { Metadata, Endpoint, HandlerFunction }; + +import retrieve_shipments from './shipments/retrieve-shipments'; +import update_shipments from './shipments/update-shipments'; +import list_shipments from './shipments/list-shipments'; +import resume_tracking_shipments from './shipments/resume-tracking-shipments'; +import stop_tracking_shipments from './shipments/stop-tracking-shipments'; +import create_tracking_requests from './tracking-requests/create-tracking-requests'; +import retrieve_tracking_requests from './tracking-requests/retrieve-tracking-requests'; +import update_tracking_requests from './tracking-requests/update-tracking-requests'; +import list_tracking_requests from './tracking-requests/list-tracking-requests'; +import create_webhooks from './webhooks/create-webhooks'; +import retrieve_webhooks from './webhooks/retrieve-webhooks'; +import update_webhooks from './webhooks/update-webhooks'; +import list_webhooks from './webhooks/list-webhooks'; +import delete_webhooks from './webhooks/delete-webhooks'; +import list_ips_webhooks from './webhooks/list-ips-webhooks'; +import retrieve_webhook_notifications from './webhook-notifications/retrieve-webhook-notifications'; +import list_webhook_notifications from './webhook-notifications/list-webhook-notifications'; +import get_examples_webhook_notifications from './webhook-notifications/get-examples-webhook-notifications'; +import retrieve_containers from './containers/retrieve-containers'; +import update_containers from './containers/update-containers'; +import list_containers from './containers/list-containers'; +import get_raw_events_containers from './containers/get-raw-events-containers'; +import get_transport_events_containers from './containers/get-transport-events-containers'; +import retrieve_shipping_lines from './shipping-lines/retrieve-shipping-lines'; +import list_shipping_lines from './shipping-lines/list-shipping-lines'; +import retrieve_metro_areas from './metro-areas/retrieve-metro-areas'; +import retrieve_ports from './ports/retrieve-ports'; +import retrieve_by_id_vessels from './vessels/retrieve-by-id-vessels'; +import retrieve_by_imo_vessels from './vessels/retrieve-by-imo-vessels'; +import retrieve_terminals from './terminals/retrieve-terminals'; +import create_parties from './parties/create-parties'; +import retrieve_parties from './parties/retrieve-parties'; +import update_parties from './parties/update-parties'; +import list_parties from './parties/list-parties'; + +export const endpoints: Endpoint[] = []; + +function addEndpoint(endpoint: Endpoint) { + endpoints.push(endpoint); +} + +addEndpoint(retrieve_shipments); +addEndpoint(update_shipments); +addEndpoint(list_shipments); +addEndpoint(resume_tracking_shipments); +addEndpoint(stop_tracking_shipments); +addEndpoint(create_tracking_requests); +addEndpoint(retrieve_tracking_requests); +addEndpoint(update_tracking_requests); +addEndpoint(list_tracking_requests); +addEndpoint(create_webhooks); +addEndpoint(retrieve_webhooks); +addEndpoint(update_webhooks); +addEndpoint(list_webhooks); +addEndpoint(delete_webhooks); +addEndpoint(list_ips_webhooks); +addEndpoint(retrieve_webhook_notifications); +addEndpoint(list_webhook_notifications); +addEndpoint(get_examples_webhook_notifications); +addEndpoint(retrieve_containers); +addEndpoint(update_containers); +addEndpoint(list_containers); +addEndpoint(get_raw_events_containers); +addEndpoint(get_transport_events_containers); +addEndpoint(retrieve_shipping_lines); +addEndpoint(list_shipping_lines); +addEndpoint(retrieve_metro_areas); +addEndpoint(retrieve_ports); +addEndpoint(retrieve_by_id_vessels); +addEndpoint(retrieve_by_imo_vessels); +addEndpoint(retrieve_terminals); +addEndpoint(create_parties); +addEndpoint(retrieve_parties); +addEndpoint(update_parties); +addEndpoint(list_parties); + +export type Filter = { + type: 'resource' | 'operation' | 'tag' | 'tool'; + op: 'include' | 'exclude'; + value: string; +}; + +export function query(filters: Filter[], endpoints: Endpoint[]): Endpoint[] { + const allExcludes = filters.length > 0 && filters.every((filter) => filter.op === 'exclude'); + const unmatchedFilters = new Set(filters); + + const filtered = endpoints.filter((endpoint: Endpoint) => { + let included = false || allExcludes; + + for (const filter of filters) { + if (match(filter, endpoint)) { + unmatchedFilters.delete(filter); + included = filter.op === 'include'; + } + } + + return included; + }); + + // Check if any filters didn't match + const unmatched = Array.from(unmatchedFilters).filter((f) => f.type === 'tool' || f.type === 'resource'); + if (unmatched.length > 0) { + throw new Error( + `The following filters did not match any endpoints: ${unmatched + .map((f) => `${f.type}=${f.value}`) + .join(', ')}`, + ); + } + + return filtered; +} + +function match({ type, value }: Filter, endpoint: Endpoint): boolean { + switch (type) { + case 'resource': { + const regexStr = '^' + normalizeResource(value).replace(/\*/g, '.*') + '$'; + const regex = new RegExp(regexStr); + return regex.test(normalizeResource(endpoint.metadata.resource)); + } + case 'operation': + return endpoint.metadata.operation === value; + case 'tag': + return endpoint.metadata.tags.includes(value); + case 'tool': + return endpoint.tool.name === value; + } +} + +function normalizeResource(resource: string): string { + return resource.toLowerCase().replace(/[^a-z.*\-_]*/g, ''); +} diff --git a/packages/mcp-server/src/tools/metro-areas/retrieve-metro-areas.ts b/packages/mcp-server/src/tools/metro-areas/retrieve-metro-areas.ts new file mode 100644 index 00000000..751c8c10 --- /dev/null +++ b/packages/mcp-server/src/tools/metro-areas/retrieve-metro-areas.ts @@ -0,0 +1,54 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'metro_areas', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/metro_areas/{id}', + operationId: 'get-metro-area-id', +}; + +export const tool: Tool = { + name: 'retrieve_metro_areas', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nReturn the details of a single metro area.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/metro_area_retrieve_response',\n $defs: {\n metro_area_retrieve_response: {\n type: 'object',\n properties: {\n data: {\n $ref: '#/$defs/metro_area'\n }\n }\n },\n metro_area: {\n type: 'object',\n title: 'Metro area model',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'metro_area'\n ]\n },\n attributes: {\n type: 'object',\n properties: {\n code: {\n type: 'string',\n description: 'UN/LOCODE'\n },\n country_code: {\n type: 'string'\n },\n latitude: {\n type: 'number'\n },\n longitude: {\n type: 'number'\n },\n name: {\n type: 'string'\n },\n state_abbr: {\n type: 'string'\n },\n time_zone: {\n type: 'string',\n description: 'IANA tz'\n }\n }\n }\n },\n required: [ 'id',\n 'type'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.metroAreas.retrieve(id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/parties/create-parties.ts b/packages/mcp-server/src/tools/parties/create-parties.ts new file mode 100644 index 00000000..cc2df468 --- /dev/null +++ b/packages/mcp-server/src/tools/parties/create-parties.ts @@ -0,0 +1,64 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'parties', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/parties', + operationId: 'post-party', +}; + +export const tool: Tool = { + name: 'create_parties', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nCreates a new party\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/party_create_response',\n $defs: {\n party_create_response: {\n type: 'object',\n properties: {\n data: {\n $ref: '#/$defs/party'\n },\n links: {\n $ref: '#/$defs/link_self'\n }\n }\n },\n party: {\n type: 'object',\n title: 'Party model',\n properties: {\n attributes: {\n type: 'object',\n properties: {\n company_name: {\n type: 'string',\n description: 'Company name'\n }\n },\n required: [ 'company_name'\n ]\n },\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'party'\n ]\n }\n },\n required: [ 'attributes'\n ]\n },\n link_self: {\n type: 'object',\n title: 'link',\n properties: {\n self: {\n type: 'string'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + data: { + type: 'object', + properties: { + attributes: { + type: 'object', + properties: { + company_name: { + type: 'string', + description: 'The name of the company', + }, + }, + }, + }, + required: ['attributes'], + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: [], + }, + annotations: {}, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.parties.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/parties/list-parties.ts b/packages/mcp-server/src/tools/parties/list-parties.ts new file mode 100644 index 00000000..7bd40f2e --- /dev/null +++ b/packages/mcp-server/src/tools/parties/list-parties.ts @@ -0,0 +1,57 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'parties', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/parties', + operationId: 'list-parties', +}; + +export const tool: Tool = { + name: 'list_parties', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet a list of parties\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/party_list_response',\n $defs: {\n party_list_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n $ref: '#/$defs/party'\n }\n },\n links: {\n $ref: '#/$defs/links'\n },\n meta: {\n $ref: '#/$defs/meta'\n }\n }\n },\n party: {\n type: 'object',\n title: 'Party model',\n properties: {\n attributes: {\n type: 'object',\n properties: {\n company_name: {\n type: 'string',\n description: 'Company name'\n }\n },\n required: [ 'company_name'\n ]\n },\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'party'\n ]\n }\n },\n required: [ 'attributes'\n ]\n },\n links: {\n type: 'object',\n title: 'links',\n properties: {\n first: {\n type: 'string'\n },\n last: {\n type: 'string'\n },\n next: {\n type: 'string'\n },\n prev: {\n type: 'string'\n },\n self: {\n type: 'string'\n }\n }\n },\n meta: {\n type: 'object',\n title: 'meta',\n properties: {\n size: {\n type: 'integer'\n },\n total: {\n type: 'integer'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + 'page[number]': { + type: 'integer', + }, + 'page[size]': { + type: 'integer', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.parties.list(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/parties/retrieve-parties.ts b/packages/mcp-server/src/tools/parties/retrieve-parties.ts new file mode 100644 index 00000000..45f16bea --- /dev/null +++ b/packages/mcp-server/src/tools/parties/retrieve-parties.ts @@ -0,0 +1,54 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'parties', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/parties/{id}', + operationId: 'get-parties-id', +}; + +export const tool: Tool = { + name: 'retrieve_parties', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nReturns a party by it's given identifier\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/party_retrieve_response',\n $defs: {\n party_retrieve_response: {\n type: 'object',\n properties: {\n data: {\n $ref: '#/$defs/party'\n },\n links: {\n $ref: '#/$defs/link_self'\n }\n }\n },\n party: {\n type: 'object',\n title: 'Party model',\n properties: {\n attributes: {\n type: 'object',\n properties: {\n company_name: {\n type: 'string',\n description: 'Company name'\n }\n },\n required: [ 'company_name'\n ]\n },\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'party'\n ]\n }\n },\n required: [ 'attributes'\n ]\n },\n link_self: {\n type: 'object',\n title: 'link',\n properties: {\n self: {\n type: 'string'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.parties.retrieve(id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/parties/update-parties.ts b/packages/mcp-server/src/tools/parties/update-parties.ts new file mode 100644 index 00000000..33ad7779 --- /dev/null +++ b/packages/mcp-server/src/tools/parties/update-parties.ts @@ -0,0 +1,67 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'parties', + operation: 'write', + tags: [], + httpMethod: 'patch', + httpPath: '/parties/{id}', + operationId: 'edit-party', +}; + +export const tool: Tool = { + name: 'update_parties', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nUpdates a party\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/party_update_response',\n $defs: {\n party_update_response: {\n type: 'object',\n properties: {\n data: {\n $ref: '#/$defs/party'\n },\n links: {\n $ref: '#/$defs/link_self'\n }\n }\n },\n party: {\n type: 'object',\n title: 'Party model',\n properties: {\n attributes: {\n type: 'object',\n properties: {\n company_name: {\n type: 'string',\n description: 'Company name'\n }\n },\n required: [ 'company_name'\n ]\n },\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'party'\n ]\n }\n },\n required: [ 'attributes'\n ]\n },\n link_self: {\n type: 'object',\n title: 'link',\n properties: {\n self: {\n type: 'string'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + data: { + type: 'object', + properties: { + attributes: { + type: 'object', + properties: { + company_name: { + type: 'string', + description: 'The name of the company', + }, + }, + }, + }, + required: ['attributes'], + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: {}, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.parties.update(id, body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/ports/retrieve-ports.ts b/packages/mcp-server/src/tools/ports/retrieve-ports.ts new file mode 100644 index 00000000..7d0c28bf --- /dev/null +++ b/packages/mcp-server/src/tools/ports/retrieve-ports.ts @@ -0,0 +1,54 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'ports', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/ports/{id}', + operationId: 'get-port-id', +}; + +export const tool: Tool = { + name: 'retrieve_ports', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nReturn the details of a single port.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/port_retrieve_response',\n $defs: {\n port_retrieve_response: {\n type: 'object',\n properties: {\n data: {\n $ref: '#/$defs/port'\n }\n }\n },\n port: {\n type: 'object',\n title: 'Port model',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'port'\n ]\n },\n attributes: {\n type: 'object',\n properties: {\n city: {\n type: 'string'\n },\n code: {\n type: 'string',\n description: 'UN/LOCODE'\n },\n country_code: {\n type: 'string',\n description: '2 digit country code'\n },\n latitude: {\n type: 'number'\n },\n longitude: {\n type: 'number'\n },\n name: {\n type: 'string'\n },\n state_abbr: {\n type: 'string'\n },\n time_zone: {\n type: 'string',\n description: 'IANA tz'\n }\n }\n }\n },\n required: [ 'id',\n 'type'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.ports.retrieve(id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/shipments/list-shipments.ts b/packages/mcp-server/src/tools/shipments/list-shipments.ts new file mode 100644 index 00000000..3cabef2d --- /dev/null +++ b/packages/mcp-server/src/tools/shipments/list-shipments.ts @@ -0,0 +1,57 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'shipments', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/shipments', + operationId: 'get-shipments', +}; + +export const tool: Tool = { + name: 'list_shipments', + description: + 'Returns a list of your shipments. The shipments are returned sorted by creation date, with the most recent shipments appearing first.\n\nThis api will return all shipments associated with the account. Shipments created via the `tracking_request` API aswell as the ones added via the dashboard will be retuned via this endpoint. ', + inputSchema: { + type: 'object', + properties: { + include: { + type: 'string', + description: 'Comma delimited list of relations to include', + }, + number: { + type: 'string', + description: 'Search shipments by the original request tracking `request_number`', + }, + 'page[number]': { + type: 'integer', + description: '\n', + }, + 'page[size]': { + type: 'integer', + description: '\n', + }, + q: { + type: 'string', + description: '\nSearch shipments by master bill of lading, reference number, or container number.', + }, + }, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const body = args as any; + return asTextContentResult(await client.shipments.list(body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/shipments/resume-tracking-shipments.ts b/packages/mcp-server/src/tools/shipments/resume-tracking-shipments.ts new file mode 100644 index 00000000..e7fbeecb --- /dev/null +++ b/packages/mcp-server/src/tools/shipments/resume-tracking-shipments.ts @@ -0,0 +1,52 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'shipments', + operation: 'write', + tags: [], + httpMethod: 'patch', + httpPath: '/shipments/{id}/resume_tracking', + operationId: 'patch-shipments-id-resume-tracking', +}; + +export const tool: Tool = { + name: 'resume_tracking_shipments', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nResume tracking a shipment. Keep in mind that some information is only made available by our data sources at specific times, so a stopped and resumed shipment may have some information missing.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/shipment_resume_tracking_response',\n $defs: {\n shipment_resume_tracking_response: {\n type: 'object',\n properties: {\n data: {\n $ref: '#/$defs/shipment'\n }\n }\n },\n shipment: {\n type: 'object',\n title: 'Shipment model',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n bill_of_lading_number: {\n type: 'string'\n },\n created_at: {\n type: 'string',\n format: 'date-time'\n },\n customer_name: {\n type: 'string'\n },\n destination_ata_at: {\n type: 'string',\n format: 'date-time'\n },\n destination_eta_at: {\n type: 'string',\n format: 'date-time'\n },\n destination_locode: {\n type: 'string',\n description: 'UN/LOCODE'\n },\n destination_name: {\n type: 'string'\n },\n destination_timezone: {\n type: 'string',\n description: 'IANA tz'\n },\n line_tracking_last_attempted_at: {\n type: 'string',\n description: 'When Terminal49 last tried to update the shipment status from the shipping line',\n format: 'date-time'\n },\n line_tracking_last_succeeded_at: {\n type: 'string',\n description: 'When Terminal49 last successfully updated the shipment status from the shipping line',\n format: 'date-time'\n },\n line_tracking_stopped_at: {\n type: 'string',\n description: 'When Terminal49 stopped checking at the shipping line',\n format: 'date-time'\n },\n line_tracking_stopped_reason: {\n type: 'string',\n description: 'The reason Terminal49 stopped checking',\n enum: [ 'all_containers_terminated',\n 'past_arrival_window',\n 'no_updates_at_line',\n 'cancelled_by_user',\n 'booking_cancelled'\n ]\n },\n normalized_number: {\n type: 'string',\n description: 'The normalized version of the shipment number used for querying the carrier'\n },\n pod_ata_at: {\n type: 'string',\n format: 'date-time'\n },\n pod_eta_at: {\n type: 'string',\n format: 'date-time'\n },\n pod_original_eta_at: {\n type: 'string',\n format: 'date-time'\n },\n pod_timezone: {\n type: 'string',\n description: 'IANA tz'\n },\n pod_vessel_imo: {\n type: 'string'\n },\n pod_vessel_name: {\n type: 'string'\n },\n pod_voyage_number: {\n type: 'string'\n },\n pol_atd_at: {\n type: 'string',\n format: 'date-time'\n },\n pol_etd_at: {\n type: 'string',\n format: 'date-time'\n },\n pol_timezone: {\n type: 'string',\n description: 'IANA tz'\n },\n port_of_discharge_locode: {\n type: 'string',\n description: 'UN/LOCODE'\n },\n port_of_discharge_name: {\n type: 'string'\n },\n port_of_lading_locode: {\n type: 'string',\n description: 'UN/LOCODE'\n },\n port_of_lading_name: {\n type: 'string'\n },\n ref_numbers: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n shipping_line_name: {\n type: 'string'\n },\n shipping_line_scac: {\n type: 'string'\n },\n shipping_line_short_name: {\n type: 'string'\n },\n tags: {\n type: 'array',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'bill_of_lading_number'\n ]\n },\n links: {\n type: 'object',\n properties: {\n self: {\n type: 'string'\n }\n },\n required: [ 'self'\n ]\n },\n relationships: {\n type: 'object',\n properties: {\n containers: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'container'\n ]\n }\n },\n required: [ 'id',\n 'type'\n ]\n }\n }\n }\n },\n destination: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'port',\n 'metro_area'\n ]\n }\n },\n required: [ 'id',\n 'type'\n ]\n }\n }\n },\n destination_terminal: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'terminal',\n 'rail_terminal'\n ]\n }\n },\n required: [ 'id',\n 'type'\n ]\n }\n }\n },\n line_tracking_stopped_by_user: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'user'\n ]\n }\n },\n required: [ 'id',\n 'type'\n ]\n }\n }\n },\n pod_terminal: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'terminal'\n ]\n }\n },\n required: [ 'id',\n 'type'\n ]\n }\n }\n },\n port_of_discharge: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'port'\n ]\n }\n },\n required: [ 'id',\n 'type'\n ]\n }\n }\n },\n port_of_lading: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'port'\n ]\n }\n },\n required: [ 'id',\n 'type'\n ]\n }\n }\n }\n }\n },\n type: {\n type: 'string',\n enum: [ 'shipment'\n ]\n }\n },\n required: [ 'id',\n 'attributes',\n 'links',\n 'relationships',\n 'type'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: {}, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.shipments.resumeTracking(id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/shipments/retrieve-shipments.ts b/packages/mcp-server/src/tools/shipments/retrieve-shipments.ts new file mode 100644 index 00000000..c3f9f67e --- /dev/null +++ b/packages/mcp-server/src/tools/shipments/retrieve-shipments.ts @@ -0,0 +1,44 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'shipments', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/shipments/{id}', + operationId: 'get-shipment-id', +}; + +export const tool: Tool = { + name: 'retrieve_shipments', + description: + 'Retrieves the details of an existing shipment. You need only supply the unique shipment `id` that was returned upon `tracking_request` creation.', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + include: { + type: 'string', + description: 'Comma delimited list of relations to include', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, ...body } = args as any; + return asTextContentResult(await client.shipments.retrieve(id, body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/shipments/stop-tracking-shipments.ts b/packages/mcp-server/src/tools/shipments/stop-tracking-shipments.ts new file mode 100644 index 00000000..57740f7b --- /dev/null +++ b/packages/mcp-server/src/tools/shipments/stop-tracking-shipments.ts @@ -0,0 +1,38 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'shipments', + operation: 'write', + tags: [], + httpMethod: 'patch', + httpPath: '/shipments/{id}/stop_tracking', + operationId: 'patch-shipments-id-stop-tracking', +}; + +export const tool: Tool = { + name: 'stop_tracking_shipments', + description: + "We'll stop tracking the shipment, which means that there will be no more updates. You can still access the shipment's previously-collected information via the API or dashboard.\n\nYou can resume tracking a shipment by calling the `resume_tracking` endpoint, but keep in mind that some information is only made available by our data sources at specific times, so a stopped and resumed shipment may have some information missing.", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + }, + required: ['id'], + }, + annotations: {}, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, ...body } = args as any; + return asTextContentResult(await client.shipments.stopTracking(id)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/shipments/update-shipments.ts b/packages/mcp-server/src/tools/shipments/update-shipments.ts new file mode 100644 index 00000000..506cd239 --- /dev/null +++ b/packages/mcp-server/src/tools/shipments/update-shipments.ts @@ -0,0 +1,62 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'shipments', + operation: 'write', + tags: [], + httpMethod: 'patch', + httpPath: '/shipments/{id}', + operationId: 'patch-shipments-id', +}; + +export const tool: Tool = { + name: 'update_shipments', + description: 'Update a shipment', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + data: { + type: 'object', + properties: { + attributes: { + type: 'object', + properties: { + ref_numbers: { + type: 'array', + description: 'Shipment ref numbers.', + items: { + type: 'string', + }, + }, + shipment_tags: { + type: 'array', + description: 'Tags related to a shipment', + items: { + type: 'string', + }, + }, + }, + }, + }, + required: ['attributes'], + }, + }, + required: ['id'], + }, + annotations: {}, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, ...body } = args as any; + return asTextContentResult(await client.shipments.update(id, body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/shipping-lines/list-shipping-lines.ts b/packages/mcp-server/src/tools/shipping-lines/list-shipping-lines.ts new file mode 100644 index 00000000..40e24312 --- /dev/null +++ b/packages/mcp-server/src/tools/shipping-lines/list-shipping-lines.ts @@ -0,0 +1,51 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'shipping_lines', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/shipping_lines', + operationId: 'get-shipping_lines', +}; + +export const tool: Tool = { + name: 'list_shipping_lines', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nReturn a list of shipping lines supported by Terminal49. \nN.B. There is no pagination for this endpoint.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/shipping_line_list_response',\n $defs: {\n shipping_line_list_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n $ref: '#/$defs/shipping_line'\n }\n },\n links: {\n $ref: '#/$defs/links'\n }\n }\n },\n shipping_line: {\n type: 'object',\n title: 'Shipping line model',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n alternative_scacs: {\n type: 'array',\n description: 'Additional SCACs which will be accepted in tracking requests',\n items: {\n type: 'string'\n }\n },\n bill_of_lading_tracking_support: {\n type: 'boolean'\n },\n booking_number_tracking_support: {\n type: 'boolean'\n },\n container_number_tracking_support: {\n type: 'boolean'\n },\n name: {\n type: 'string'\n },\n scac: {\n type: 'string'\n },\n short_name: {\n type: 'string'\n }\n },\n required: [ 'alternative_scacs',\n 'bill_of_lading_tracking_support',\n 'booking_number_tracking_support',\n 'container_number_tracking_support',\n 'name',\n 'scac',\n 'short_name'\n ]\n },\n type: {\n type: 'string',\n enum: [ 'shipping_line'\n ]\n }\n },\n required: [ 'id',\n 'attributes',\n 'type'\n ]\n },\n links: {\n type: 'object',\n title: 'links',\n properties: {\n first: {\n type: 'string'\n },\n last: {\n type: 'string'\n },\n next: {\n type: 'string'\n },\n prev: {\n type: 'string'\n },\n self: {\n type: 'string'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { jq_filter } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.shippingLines.list())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/shipping-lines/retrieve-shipping-lines.ts b/packages/mcp-server/src/tools/shipping-lines/retrieve-shipping-lines.ts new file mode 100644 index 00000000..0633803f --- /dev/null +++ b/packages/mcp-server/src/tools/shipping-lines/retrieve-shipping-lines.ts @@ -0,0 +1,54 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'shipping_lines', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/shipping_lines/{id}', + operationId: 'get-shipping_lines-id', +}; + +export const tool: Tool = { + name: 'retrieve_shipping_lines', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nReturn the details of a single shipping line.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/shipping_line_retrieve_response',\n $defs: {\n shipping_line_retrieve_response: {\n type: 'object',\n properties: {\n data: {\n $ref: '#/$defs/shipping_line'\n }\n }\n },\n shipping_line: {\n type: 'object',\n title: 'Shipping line model',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n alternative_scacs: {\n type: 'array',\n description: 'Additional SCACs which will be accepted in tracking requests',\n items: {\n type: 'string'\n }\n },\n bill_of_lading_tracking_support: {\n type: 'boolean'\n },\n booking_number_tracking_support: {\n type: 'boolean'\n },\n container_number_tracking_support: {\n type: 'boolean'\n },\n name: {\n type: 'string'\n },\n scac: {\n type: 'string'\n },\n short_name: {\n type: 'string'\n }\n },\n required: [ 'alternative_scacs',\n 'bill_of_lading_tracking_support',\n 'booking_number_tracking_support',\n 'container_number_tracking_support',\n 'name',\n 'scac',\n 'short_name'\n ]\n },\n type: {\n type: 'string',\n enum: [ 'shipping_line'\n ]\n }\n },\n required: [ 'id',\n 'attributes',\n 'type'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.shippingLines.retrieve(id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/terminals/retrieve-terminals.ts b/packages/mcp-server/src/tools/terminals/retrieve-terminals.ts new file mode 100644 index 00000000..2c592441 --- /dev/null +++ b/packages/mcp-server/src/tools/terminals/retrieve-terminals.ts @@ -0,0 +1,54 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'terminals', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/terminals/{id}', + operationId: 'get-terminal-id', +}; + +export const tool: Tool = { + name: 'retrieve_terminals', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nReturn the details of a single terminal.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/terminal_retrieve_response',\n $defs: {\n terminal_retrieve_response: {\n type: 'object',\n properties: {\n data: {\n $ref: '#/$defs/terminal'\n }\n }\n },\n terminal: {\n type: 'object',\n title: 'Terminal model',\n properties: {\n attributes: {\n type: 'object',\n properties: {\n name: {\n type: 'string'\n },\n bic_facility_code: {\n type: 'string',\n description: 'BIC Facility Code'\n },\n city: {\n type: 'string',\n description: 'City part of the address'\n },\n country: {\n type: 'string',\n description: 'Country part of the address'\n },\n firms_code: {\n type: 'string',\n description: 'CBP FIRMS Code or CBS Sublocation Code'\n },\n nickname: {\n type: 'string'\n },\n smdg_code: {\n type: 'string',\n description: 'SMDG Code'\n },\n state: {\n type: 'string',\n description: 'State part of the address'\n },\n state_abbr: {\n type: 'string',\n description: 'State abbreviation for the state'\n },\n street: {\n type: 'string',\n description: 'Street part of the address'\n },\n zip: {\n type: 'string',\n description: 'ZIP code part of the address'\n }\n },\n required: [ 'name'\n ]\n },\n relationships: {\n type: 'object',\n properties: {\n port: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'port'\n ]\n }\n }\n }\n }\n }\n },\n required: [ 'port'\n ]\n },\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'terminal'\n ]\n }\n },\n required: [ 'attributes',\n 'relationships'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.terminals.retrieve(id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tracking-requests/create-tracking-requests.ts b/packages/mcp-server/src/tools/tracking-requests/create-tracking-requests.ts new file mode 100644 index 00000000..5be02ba0 --- /dev/null +++ b/packages/mcp-server/src/tools/tracking-requests/create-tracking-requests.ts @@ -0,0 +1,115 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'tracking_requests', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/tracking_requests', + operationId: 'post-track', +}; + +export const tool: Tool = { + name: 'create_tracking_requests', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nTo track an ocean shipment, you create a new tracking request. \nTwo attributes are required to track a shipment. A `bill of lading/booking number` and a shipping line `SCAC`. \n\nOnce a tracking request is created we will attempt to fetch the shipment details and it's related containers from the shipping line. If the attempt is successful we will create in new shipment object including any related container objects. We will send a `tracking_request.succeeded` webhook notification to your webhooks. \n\nIf the attempt to fetch fails then we will send a `tracking_request.failed` webhook notification to your `webhooks`. \n\nA `tracking_request.succeeded` or `tracking_request.failed` webhook notificaiton will only be sent if you have atleast one active webhook. \n\n# Response Schema\n```json\n{\n $ref: '#/$defs/tracking_request_create_response',\n $defs: {\n tracking_request_create_response: {\n type: 'object',\n properties: {\n data: {\n $ref: '#/$defs/tracking_request'\n },\n included: {\n type: 'array',\n items: {\n anyOf: [ {\n $ref: '#/$defs/account'\n },\n {\n $ref: '#/$defs/shipping_line'\n }\n ]\n }\n }\n }\n },\n tracking_request: {\n type: 'object',\n title: 'Tracking Request',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'tracking_request'\n ]\n },\n attributes: {\n type: 'object',\n properties: {\n created_at: {\n type: 'string',\n format: 'date-time'\n },\n request_number: {\n type: 'string'\n },\n request_type: {\n type: 'string',\n enum: [ 'bill_of_lading',\n 'booking_number',\n 'container'\n ]\n },\n scac: {\n type: 'string'\n },\n status: {\n type: 'string',\n enum: [ 'pending',\n 'awaiting_manifest',\n 'created',\n 'failed',\n 'tracking_stopped'\n ]\n },\n failed_reason: {\n type: 'string',\n description: 'If the tracking request has failed, or is currently failing, the last reason we were unable to complete the request',\n enum: [ 'booking_cancelled',\n 'duplicate',\n 'expired',\n 'internal_processing_error',\n 'invalid_number',\n 'not_found',\n 'retries_exhausted',\n 'shipping_line_unreachable',\n 'unrecognized_response',\n 'data_unavailable'\n ]\n },\n is_retrying: {\n type: 'boolean'\n },\n ref_numbers: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n retry_count: {\n type: 'integer',\n description: 'How many times T49 has attempted to get the shipment from the shipping line'\n },\n tags: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n updated_at: {\n type: 'string',\n format: 'date-time'\n }\n },\n required: [ 'created_at',\n 'request_number',\n 'request_type',\n 'scac',\n 'status'\n ]\n },\n relationships: {\n type: 'object',\n properties: {\n customer: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'party'\n ]\n }\n }\n }\n }\n },\n tracked_object: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'shipment'\n ]\n }\n }\n }\n }\n }\n }\n }\n },\n required: [ 'id',\n 'type'\n ]\n },\n account: {\n type: 'object',\n title: 'Account model',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n company_name: {\n type: 'string'\n }\n },\n required: [ 'company_name'\n ]\n },\n type: {\n type: 'string',\n enum: [ 'container'\n ]\n }\n },\n required: [ 'id',\n 'attributes',\n 'type'\n ]\n },\n shipping_line: {\n type: 'object',\n title: 'Shipping line model',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n alternative_scacs: {\n type: 'array',\n description: 'Additional SCACs which will be accepted in tracking requests',\n items: {\n type: 'string'\n }\n },\n bill_of_lading_tracking_support: {\n type: 'boolean'\n },\n booking_number_tracking_support: {\n type: 'boolean'\n },\n container_number_tracking_support: {\n type: 'boolean'\n },\n name: {\n type: 'string'\n },\n scac: {\n type: 'string'\n },\n short_name: {\n type: 'string'\n }\n },\n required: [ 'alternative_scacs',\n 'bill_of_lading_tracking_support',\n 'booking_number_tracking_support',\n 'container_number_tracking_support',\n 'name',\n 'scac',\n 'short_name'\n ]\n },\n type: {\n type: 'string',\n enum: [ 'shipping_line'\n ]\n }\n },\n required: [ 'id',\n 'attributes',\n 'type'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + data: { + type: 'object', + properties: { + type: { + type: 'string', + enum: ['tracking_request'], + }, + attributes: { + type: 'object', + properties: { + request_number: { + type: 'string', + }, + request_type: { + type: 'string', + description: + ' The type of document number to be supplied. Container number support is currently in BETA.', + enum: ['bill_of_lading', 'booking_number', 'container'], + }, + scac: { + type: 'string', + }, + ref_numbers: { + type: 'array', + description: + 'Optional list of reference numbers to be added to the shipment when tracking request completes', + items: { + type: 'string', + }, + }, + shipment_tags: { + type: 'array', + description: + 'Optional list of tags to be added to the shipment when tracking request completes', + items: { + type: 'string', + }, + }, + }, + required: ['request_number', 'request_type', 'scac'], + }, + relationships: { + type: 'object', + properties: { + customer: { + type: 'object', + properties: { + data: { + type: 'object', + properties: { + id: { + type: 'string', + }, + type: { + type: 'string', + enum: ['party'], + }, + }, + }, + }, + }, + }, + }, + }, + required: ['type'], + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: [], + }, + annotations: {}, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.trackingRequests.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tracking-requests/list-tracking-requests.ts b/packages/mcp-server/src/tools/tracking-requests/list-tracking-requests.ts new file mode 100644 index 00000000..204bb03a --- /dev/null +++ b/packages/mcp-server/src/tools/tracking-requests/list-tracking-requests.ts @@ -0,0 +1,88 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'tracking_requests', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/tracking_requests', + operationId: 'get-tracking-requests', +}; + +export const tool: Tool = { + name: 'list_tracking_requests', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nReturns a list of your tracking requests. The tracking requests are returned sorted by creation date, with the most recent tracking request appearing first.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/tracking_request_list_response',\n $defs: {\n tracking_request_list_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n $ref: '#/$defs/tracking_request'\n }\n },\n included: {\n type: 'array',\n items: {\n anyOf: [ {\n $ref: '#/$defs/account'\n },\n {\n $ref: '#/$defs/shipping_line'\n },\n {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n links: {\n type: 'object',\n properties: {\n self: {\n type: 'string'\n }\n }\n },\n type: {\n type: 'string',\n enum: [ 'shipment'\n ]\n }\n }\n }\n ]\n }\n },\n links: {\n $ref: '#/$defs/links'\n },\n meta: {\n $ref: '#/$defs/meta'\n }\n }\n },\n tracking_request: {\n type: 'object',\n title: 'Tracking Request',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'tracking_request'\n ]\n },\n attributes: {\n type: 'object',\n properties: {\n created_at: {\n type: 'string',\n format: 'date-time'\n },\n request_number: {\n type: 'string'\n },\n request_type: {\n type: 'string',\n enum: [ 'bill_of_lading',\n 'booking_number',\n 'container'\n ]\n },\n scac: {\n type: 'string'\n },\n status: {\n type: 'string',\n enum: [ 'pending',\n 'awaiting_manifest',\n 'created',\n 'failed',\n 'tracking_stopped'\n ]\n },\n failed_reason: {\n type: 'string',\n description: 'If the tracking request has failed, or is currently failing, the last reason we were unable to complete the request',\n enum: [ 'booking_cancelled',\n 'duplicate',\n 'expired',\n 'internal_processing_error',\n 'invalid_number',\n 'not_found',\n 'retries_exhausted',\n 'shipping_line_unreachable',\n 'unrecognized_response',\n 'data_unavailable'\n ]\n },\n is_retrying: {\n type: 'boolean'\n },\n ref_numbers: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n retry_count: {\n type: 'integer',\n description: 'How many times T49 has attempted to get the shipment from the shipping line'\n },\n tags: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n updated_at: {\n type: 'string',\n format: 'date-time'\n }\n },\n required: [ 'created_at',\n 'request_number',\n 'request_type',\n 'scac',\n 'status'\n ]\n },\n relationships: {\n type: 'object',\n properties: {\n customer: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'party'\n ]\n }\n }\n }\n }\n },\n tracked_object: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'shipment'\n ]\n }\n }\n }\n }\n }\n }\n }\n },\n required: [ 'id',\n 'type'\n ]\n },\n account: {\n type: 'object',\n title: 'Account model',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n company_name: {\n type: 'string'\n }\n },\n required: [ 'company_name'\n ]\n },\n type: {\n type: 'string',\n enum: [ 'container'\n ]\n }\n },\n required: [ 'id',\n 'attributes',\n 'type'\n ]\n },\n shipping_line: {\n type: 'object',\n title: 'Shipping line model',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n alternative_scacs: {\n type: 'array',\n description: 'Additional SCACs which will be accepted in tracking requests',\n items: {\n type: 'string'\n }\n },\n bill_of_lading_tracking_support: {\n type: 'boolean'\n },\n booking_number_tracking_support: {\n type: 'boolean'\n },\n container_number_tracking_support: {\n type: 'boolean'\n },\n name: {\n type: 'string'\n },\n scac: {\n type: 'string'\n },\n short_name: {\n type: 'string'\n }\n },\n required: [ 'alternative_scacs',\n 'bill_of_lading_tracking_support',\n 'booking_number_tracking_support',\n 'container_number_tracking_support',\n 'name',\n 'scac',\n 'short_name'\n ]\n },\n type: {\n type: 'string',\n enum: [ 'shipping_line'\n ]\n }\n },\n required: [ 'id',\n 'attributes',\n 'type'\n ]\n },\n links: {\n type: 'object',\n title: 'links',\n properties: {\n first: {\n type: 'string'\n },\n last: {\n type: 'string'\n },\n next: {\n type: 'string'\n },\n prev: {\n type: 'string'\n },\n self: {\n type: 'string'\n }\n }\n },\n meta: {\n type: 'object',\n title: 'meta',\n properties: {\n size: {\n type: 'integer'\n },\n total: {\n type: 'integer'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + 'filter[created_at][end]': { + type: 'string', + description: 'filter by tracking_requests `created_at` before a certain ISO8601 timestamp', + format: 'date-time', + }, + 'filter[created_at][start]': { + type: 'string', + description: 'filter by tracking_requests `created_at` after a certain ISO8601 timestamp', + format: 'date-time', + }, + 'filter[request_number]': { + type: 'string', + description: 'filter by `request_number`', + }, + 'filter[scac]': { + type: 'string', + description: 'filter by shipping line `scac`', + }, + 'filter[status]': { + type: 'string', + description: 'filter by `status`', + enum: ['created', 'pending', 'failed'], + }, + include: { + type: 'string', + description: "Comma delimited list of relations to include. 'tracked_object' is included by default.", + }, + 'page[number]': { + type: 'integer', + }, + 'page[size]': { + type: 'integer', + }, + q: { + type: 'string', + description: 'A search term to be applied against request_number and reference_numbers.', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.trackingRequests.list(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tracking-requests/retrieve-tracking-requests.ts b/packages/mcp-server/src/tools/tracking-requests/retrieve-tracking-requests.ts new file mode 100644 index 00000000..51f3e202 --- /dev/null +++ b/packages/mcp-server/src/tools/tracking-requests/retrieve-tracking-requests.ts @@ -0,0 +1,43 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'tracking_requests', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/tracking_requests/{id}', + operationId: 'get-track-request-by-id', +}; + +export const tool: Tool = { + name: 'retrieve_tracking_requests', + description: 'Get the details and status of an existing tracking request. ', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + include: { + type: 'string', + description: "Comma delimited list of relations to include. 'tracked_object' is included by default.", + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, ...body } = args as any; + return asTextContentResult(await client.trackingRequests.retrieve(id, body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tracking-requests/update-tracking-requests.ts b/packages/mcp-server/src/tools/tracking-requests/update-tracking-requests.ts new file mode 100644 index 00000000..fa5f719a --- /dev/null +++ b/packages/mcp-server/src/tools/tracking-requests/update-tracking-requests.ts @@ -0,0 +1,67 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'tracking_requests', + operation: 'write', + tags: [], + httpMethod: 'patch', + httpPath: '/tracking_requests/{id}', + operationId: 'patch-track-request-by-id', +}; + +export const tool: Tool = { + name: 'update_tracking_requests', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nUpdate a tracking request\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/tracking_request_update_response',\n $defs: {\n tracking_request_update_response: {\n type: 'object',\n properties: {\n data: {\n $ref: '#/$defs/tracking_request'\n }\n }\n },\n tracking_request: {\n type: 'object',\n title: 'Tracking Request',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'tracking_request'\n ]\n },\n attributes: {\n type: 'object',\n properties: {\n created_at: {\n type: 'string',\n format: 'date-time'\n },\n request_number: {\n type: 'string'\n },\n request_type: {\n type: 'string',\n enum: [ 'bill_of_lading',\n 'booking_number',\n 'container'\n ]\n },\n scac: {\n type: 'string'\n },\n status: {\n type: 'string',\n enum: [ 'pending',\n 'awaiting_manifest',\n 'created',\n 'failed',\n 'tracking_stopped'\n ]\n },\n failed_reason: {\n type: 'string',\n description: 'If the tracking request has failed, or is currently failing, the last reason we were unable to complete the request',\n enum: [ 'booking_cancelled',\n 'duplicate',\n 'expired',\n 'internal_processing_error',\n 'invalid_number',\n 'not_found',\n 'retries_exhausted',\n 'shipping_line_unreachable',\n 'unrecognized_response',\n 'data_unavailable'\n ]\n },\n is_retrying: {\n type: 'boolean'\n },\n ref_numbers: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n retry_count: {\n type: 'integer',\n description: 'How many times T49 has attempted to get the shipment from the shipping line'\n },\n tags: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n updated_at: {\n type: 'string',\n format: 'date-time'\n }\n },\n required: [ 'created_at',\n 'request_number',\n 'request_type',\n 'scac',\n 'status'\n ]\n },\n relationships: {\n type: 'object',\n properties: {\n customer: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'party'\n ]\n }\n }\n }\n }\n },\n tracked_object: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'shipment'\n ]\n }\n }\n }\n }\n }\n }\n }\n },\n required: [ 'id',\n 'type'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + data: { + type: 'object', + properties: { + attributes: { + type: 'object', + properties: { + ref_number: { + type: 'string', + description: 'Tracking request ref number.', + }, + }, + }, + }, + required: ['attributes'], + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: {}, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.trackingRequests.update(id, body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/types.ts b/packages/mcp-server/src/tools/types.ts new file mode 100644 index 00000000..09ce55f1 --- /dev/null +++ b/packages/mcp-server/src/tools/types.ts @@ -0,0 +1,115 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Terminal49 from 'terminal49'; +import { Tool } from '@modelcontextprotocol/sdk/types.js'; + +type TextContentBlock = { + type: 'text'; + text: string; +}; + +type ImageContentBlock = { + type: 'image'; + data: string; + mimeType: string; +}; + +type AudioContentBlock = { + type: 'audio'; + data: string; + mimeType: string; +}; + +type ResourceContentBlock = { + type: 'resource'; + resource: + | { + uri: string; + mimeType: string; + text: string; + } + | { + uri: string; + mimeType: string; + blob: string; + }; +}; + +export type ContentBlock = TextContentBlock | ImageContentBlock | AudioContentBlock | ResourceContentBlock; + +export type ToolCallResult = { + content: ContentBlock[]; + isError?: boolean; +}; + +export type HandlerFunction = ( + client: Terminal49, + args: Record | undefined, +) => Promise; + +export function asTextContentResult(result: unknown): ToolCallResult { + return { + content: [ + { + type: 'text', + text: JSON.stringify(result, null, 2), + }, + ], + }; +} + +export async function asBinaryContentResult(response: Response): Promise { + const blob = await response.blob(); + const mimeType = blob.type; + const data = Buffer.from(await blob.arrayBuffer()).toString('base64'); + if (mimeType.startsWith('image/')) { + return { + content: [{ type: 'image', mimeType, data }], + }; + } else if (mimeType.startsWith('audio/')) { + return { + content: [{ type: 'audio', mimeType, data }], + }; + } else { + return { + content: [ + { + type: 'resource', + resource: { + // We must give a URI, even though this isn't actually an MCP resource. + uri: 'resource://tool-response', + mimeType, + blob: data, + }, + }, + ], + }; + } +} + +export function asErrorResult(message: string): ToolCallResult { + return { + content: [ + { + type: 'text', + text: message, + }, + ], + isError: true, + }; +} + +export type Metadata = { + resource: string; + operation: 'read' | 'write'; + tags: string[]; + httpMethod?: string; + httpPath?: string; + operationId?: string; +}; + +export type Endpoint = { + metadata: Metadata; + tool: Tool; + handler: HandlerFunction; +}; diff --git a/packages/mcp-server/src/tools/vessels/retrieve-by-id-vessels.ts b/packages/mcp-server/src/tools/vessels/retrieve-by-id-vessels.ts new file mode 100644 index 00000000..177e7111 --- /dev/null +++ b/packages/mcp-server/src/tools/vessels/retrieve-by-id-vessels.ts @@ -0,0 +1,54 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'vessels', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/vessels/{id}', + operationId: 'get-vessels-id', +}; + +export const tool: Tool = { + name: 'retrieve_by_id_vessels', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nReturns a vessel by it's given identifier\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/vessel_retrieve_by_id_response',\n $defs: {\n vessel_retrieve_by_id_response: {\n type: 'object',\n properties: {\n data: {\n $ref: '#/$defs/vessel'\n }\n }\n },\n vessel: {\n type: 'object',\n title: 'vessel',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n imo: {\n type: 'string',\n description: 'International Maritime Organization (IMO) number'\n },\n latitude: {\n type: 'number',\n description: 'The current latitude position of the vessel'\n },\n longitude: {\n type: 'number',\n description: 'The current longitude position of the vessel'\n },\n mmsi: {\n type: 'string',\n description: 'Maritime Mobile Service Identity (MMSI)'\n },\n name: {\n type: 'string',\n description: 'The name of the ship or vessel'\n },\n nautical_speed_knots: {\n type: 'number',\n description: 'The current speed of the ship in knots (nautical miles per hour)'\n },\n navigational_heading_degrees: {\n type: 'number',\n description: 'The current heading of the ship in degrees, where 0 is North, 90 is East, 180 is South, and 270 is West'\n },\n position_timestamp: {\n type: 'string',\n description: 'The timestamp of when the ship\\'s position was last recorded, in ISO 8601 date and time format'\n }\n }\n },\n type: {\n type: 'string',\n enum: [ 'vessel'\n ]\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.vessels.retrieveByID(id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/vessels/retrieve-by-imo-vessels.ts b/packages/mcp-server/src/tools/vessels/retrieve-by-imo-vessels.ts new file mode 100644 index 00000000..bbab8d23 --- /dev/null +++ b/packages/mcp-server/src/tools/vessels/retrieve-by-imo-vessels.ts @@ -0,0 +1,54 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'vessels', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/vessels/{imo}', + operationId: 'get-vessels-imo', +}; + +export const tool: Tool = { + name: 'retrieve_by_imo_vessels', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nReturns a vessel by the given IMO number.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/vessel_retrieve_by_imo_response',\n $defs: {\n vessel_retrieve_by_imo_response: {\n type: 'object',\n properties: {\n data: {\n $ref: '#/$defs/vessel'\n }\n }\n },\n vessel: {\n type: 'object',\n title: 'vessel',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n imo: {\n type: 'string',\n description: 'International Maritime Organization (IMO) number'\n },\n latitude: {\n type: 'number',\n description: 'The current latitude position of the vessel'\n },\n longitude: {\n type: 'number',\n description: 'The current longitude position of the vessel'\n },\n mmsi: {\n type: 'string',\n description: 'Maritime Mobile Service Identity (MMSI)'\n },\n name: {\n type: 'string',\n description: 'The name of the ship or vessel'\n },\n nautical_speed_knots: {\n type: 'number',\n description: 'The current speed of the ship in knots (nautical miles per hour)'\n },\n navigational_heading_degrees: {\n type: 'number',\n description: 'The current heading of the ship in degrees, where 0 is North, 90 is East, 180 is South, and 270 is West'\n },\n position_timestamp: {\n type: 'string',\n description: 'The timestamp of when the ship\\'s position was last recorded, in ISO 8601 date and time format'\n }\n }\n },\n type: {\n type: 'string',\n enum: [ 'vessel'\n ]\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + imo: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['imo'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { imo, jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.vessels.retrieveByImo(imo))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/webhook-notifications/get-examples-webhook-notifications.ts b/packages/mcp-server/src/tools/webhook-notifications/get-examples-webhook-notifications.ts new file mode 100644 index 00000000..2da003a1 --- /dev/null +++ b/packages/mcp-server/src/tools/webhook-notifications/get-examples-webhook-notifications.ts @@ -0,0 +1,75 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'webhook_notifications', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/webhook_notifications/examples', + operationId: 'get-webhook-notifications-example', +}; + +export const tool: Tool = { + name: 'get_examples_webhook_notifications', + description: + 'Returns an example payload as it would be sent to a webhook endpoint for the provided `event` ', + inputSchema: { + type: 'object', + properties: { + event: { + type: 'string', + description: 'The webhook notification event name you wish to see an example of', + enum: [ + 'container.transport.vessel_arrived', + 'container.transport.vessel_discharged', + 'container.transport.vessel_loaded', + 'container.transport.vessel_departed', + 'container.transport.rail_departed', + 'container.transport.rail_arrived', + 'container.transport.rail_loaded', + 'container.transport.rail_unloaded', + 'container.transport.transshipment_arrived', + 'container.transport.transshipment_discharged', + 'container.transport.transshipment_loaded', + 'container.transport.transshipment_departed', + 'container.transport.feeder_arrived', + 'container.transport.feeder_discharged', + 'container.transport.feeder_loaded', + 'container.transport.feeder_departed', + 'container.transport.empty_out', + 'container.transport.full_in', + 'container.transport.full_out', + 'container.transport.empty_in', + 'container.transport.vessel_berthed', + 'shipment.estimated.arrival', + 'tracking_request.succeeded', + 'tracking_request.failed', + 'tracking_request.awaiting_manifest', + 'tracking_request.tracking_stopped', + 'container.created', + 'container.updated', + 'container.pod_terminal_changed', + 'container.transport.arrived_at_inland_destination', + 'container.transport.estimated.arrived_at_inland_destination', + 'container.pickup_lfd.changed', + ], + }, + }, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const body = args as any; + return asTextContentResult(await client.webhookNotifications.getExamples(body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/webhook-notifications/list-webhook-notifications.ts b/packages/mcp-server/src/tools/webhook-notifications/list-webhook-notifications.ts new file mode 100644 index 00000000..35ed8fcd --- /dev/null +++ b/packages/mcp-server/src/tools/webhook-notifications/list-webhook-notifications.ts @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'webhook_notifications', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/webhook_notifications', + operationId: 'get-webhook-notifications', +}; + +export const tool: Tool = { + name: 'list_webhook_notifications', + description: + 'Return the list of webhook notifications. This can be useful for reconciling your data if your endpoint has been down. ', + inputSchema: { + type: 'object', + properties: { + include: { + type: 'string', + description: 'Comma delimited list of relations to include.', + }, + 'page[number]': { + type: 'integer', + }, + 'page[size]': { + type: 'integer', + }, + }, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const body = args as any; + return asTextContentResult(await client.webhookNotifications.list(body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/webhook-notifications/retrieve-webhook-notifications.ts b/packages/mcp-server/src/tools/webhook-notifications/retrieve-webhook-notifications.ts new file mode 100644 index 00000000..da2ad01e --- /dev/null +++ b/packages/mcp-server/src/tools/webhook-notifications/retrieve-webhook-notifications.ts @@ -0,0 +1,43 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'webhook_notifications', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/webhook_notifications/{id}', + operationId: 'get-webhook-notification-id', +}; + +export const tool: Tool = { + name: 'retrieve_webhook_notifications', + description: '\n', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + include: { + type: 'string', + description: 'Comma delimited list of relations to include.', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, ...body } = args as any; + return asTextContentResult(await client.webhookNotifications.retrieve(id, body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/webhooks/create-webhooks.ts b/packages/mcp-server/src/tools/webhooks/create-webhooks.ts new file mode 100644 index 00000000..76156b81 --- /dev/null +++ b/packages/mcp-server/src/tools/webhooks/create-webhooks.ts @@ -0,0 +1,130 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'webhooks', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/webhooks', + operationId: 'post-webhooks', +}; + +export const tool: Tool = { + name: 'create_webhooks', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nYou can configure a webhook via the API to be notified about events that happen in your Terminal49 account. These events can be realted to tracking_requests, shipments and containers. \n\nThis is the recommended way tracking shipments and containers via the API. You should use this instead of polling our the API periodically. \n\n# Response Schema\n```json\n{\n $ref: '#/$defs/webhook_create_response',\n $defs: {\n webhook_create_response: {\n type: 'object',\n properties: {\n data: {\n $ref: '#/$defs/webhook'\n }\n }\n },\n webhook: {\n type: 'object',\n title: 'webhook',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'webhook'\n ]\n },\n attributes: {\n type: 'object',\n properties: {\n active: {\n type: 'boolean',\n description: 'Whether the webhook will be delivered when events are triggered'\n },\n events: {\n type: 'array',\n description: 'The list of events to enabled for this endpoint',\n items: {\n type: 'string',\n enum: [ 'container.transport.vessel_arrived',\n 'container.transport.vessel_discharged',\n 'container.transport.vessel_loaded',\n 'container.transport.vessel_departed',\n 'container.transport.rail_departed',\n 'container.transport.rail_arrived',\n 'container.transport.rail_loaded',\n 'container.transport.rail_unloaded',\n 'container.transport.transshipment_arrived',\n 'container.transport.transshipment_discharged',\n 'container.transport.transshipment_loaded',\n 'container.transport.transshipment_departed',\n 'container.transport.feeder_arrived',\n 'container.transport.feeder_discharged',\n 'container.transport.feeder_loaded',\n 'container.transport.feeder_departed',\n 'container.transport.empty_out',\n 'container.transport.full_in',\n 'container.transport.full_out',\n 'container.transport.empty_in',\n 'container.transport.vessel_berthed',\n 'shipment.estimated.arrival',\n 'tracking_request.succeeded',\n 'tracking_request.failed',\n 'tracking_request.awaiting_manifest',\n 'tracking_request.tracking_stopped',\n 'container.created',\n 'container.updated',\n 'container.pod_terminal_changed',\n 'container.transport.arrived_at_inland_destination',\n 'container.transport.estimated.arrived_at_inland_destination',\n 'container.pickup_lfd.changed'\n ]\n }\n },\n secret: {\n type: 'string',\n description: 'A random token that will sign all delivered webhooks'\n },\n url: {\n type: 'string',\n description: 'https end point'\n },\n headers: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n name: {\n type: 'string'\n },\n value: {\n type: 'string'\n }\n }\n }\n }\n },\n required: [ 'active',\n 'events',\n 'secret',\n 'url'\n ]\n }\n },\n required: [ 'id',\n 'type'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + data: { + type: 'object', + properties: { + attributes: { + type: 'object', + properties: { + active: { + type: 'boolean', + }, + url: { + type: 'string', + description: 'The URL of the webhook endpoint.', + }, + events: { + type: 'array', + description: 'The list of events to enable for this endpoint.', + items: { + type: 'string', + enum: [ + 'container.transport.vessel_arrived', + 'container.transport.vessel_discharged', + 'container.transport.vessel_loaded', + 'container.transport.vessel_departed', + 'container.transport.rail_departed', + 'container.transport.rail_arrived', + 'container.transport.rail_loaded', + 'container.transport.rail_unloaded', + 'container.transport.transshipment_arrived', + 'container.transport.transshipment_discharged', + 'container.transport.transshipment_loaded', + 'container.transport.transshipment_departed', + 'container.transport.feeder_arrived', + 'container.transport.feeder_discharged', + 'container.transport.feeder_loaded', + 'container.transport.feeder_departed', + 'container.transport.empty_out', + 'container.transport.full_in', + 'container.transport.full_out', + 'container.transport.empty_in', + 'container.transport.vessel_berthed', + 'shipment.estimated.arrival', + 'tracking_request.succeeded', + 'tracking_request.failed', + 'tracking_request.awaiting_manifest', + 'tracking_request.tracking_stopped', + 'container.created', + 'container.updated', + 'container.pod_terminal_changed', + 'container.transport.arrived_at_inland_destination', + 'container.transport.estimated.arrived_at_inland_destination', + 'container.pickup_lfd.changed', + ], + }, + }, + headers: { + type: 'array', + description: 'Optional custom headers to pass with each webhook invocation', + items: { + type: 'object', + properties: { + name: { + type: 'string', + description: 'The name of the header. (Please note this will be auto-capitalized) ', + }, + value: { + type: 'string', + description: 'The value to pass for the header\n', + }, + }, + }, + }, + }, + required: ['active', 'url'], + }, + type: { + type: 'string', + enum: ['webhook'], + }, + }, + required: ['attributes', 'type'], + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['data'], + }, + annotations: {}, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.webhooks.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/webhooks/delete-webhooks.ts b/packages/mcp-server/src/tools/webhooks/delete-webhooks.ts new file mode 100644 index 00000000..452c24d2 --- /dev/null +++ b/packages/mcp-server/src/tools/webhooks/delete-webhooks.ts @@ -0,0 +1,40 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'webhooks', + operation: 'write', + tags: [], + httpMethod: 'delete', + httpPath: '/webhooks/{id}', + operationId: 'delete-webhooks-id', +}; + +export const tool: Tool = { + name: 'delete_webhooks', + description: 'Delete a webhook', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + }, + required: ['id'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, ...body } = args as any; + const response = await client.webhooks.delete(id).asResponse(); + return asTextContentResult(await response.text()); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/webhooks/list-ips-webhooks.ts b/packages/mcp-server/src/tools/webhooks/list-ips-webhooks.ts new file mode 100644 index 00000000..9219fbc8 --- /dev/null +++ b/packages/mcp-server/src/tools/webhooks/list-ips-webhooks.ts @@ -0,0 +1,51 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'webhooks', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/webhooks/ips', + operationId: 'get-webhooks-ips', +}; + +export const tool: Tool = { + name: 'list_ips_webhooks', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nReturn the list of IPs used for sending webhook notifications. This can be useful for whitelisting the IPs on the firewall.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/webhook_list_ips_response',\n $defs: {\n webhook_list_ips_response: {\n type: 'object',\n properties: {\n last_updated: {\n type: 'string',\n format: 'date-time'\n },\n webhook_notification_ips: {\n type: 'array',\n items: {\n type: 'string'\n }\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { jq_filter } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.webhooks.listIPs())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/webhooks/list-webhooks.ts b/packages/mcp-server/src/tools/webhooks/list-webhooks.ts new file mode 100644 index 00000000..a4f34462 --- /dev/null +++ b/packages/mcp-server/src/tools/webhooks/list-webhooks.ts @@ -0,0 +1,57 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'webhooks', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/webhooks', + operationId: 'get-webhooks', +}; + +export const tool: Tool = { + name: 'list_webhooks', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet a list of all the webhooks\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/webhook_list_response',\n $defs: {\n webhook_list_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n $ref: '#/$defs/webhook'\n }\n },\n links: {\n $ref: '#/$defs/links'\n },\n meta: {\n $ref: '#/$defs/meta'\n }\n }\n },\n webhook: {\n type: 'object',\n title: 'webhook',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'webhook'\n ]\n },\n attributes: {\n type: 'object',\n properties: {\n active: {\n type: 'boolean',\n description: 'Whether the webhook will be delivered when events are triggered'\n },\n events: {\n type: 'array',\n description: 'The list of events to enabled for this endpoint',\n items: {\n type: 'string',\n enum: [ 'container.transport.vessel_arrived',\n 'container.transport.vessel_discharged',\n 'container.transport.vessel_loaded',\n 'container.transport.vessel_departed',\n 'container.transport.rail_departed',\n 'container.transport.rail_arrived',\n 'container.transport.rail_loaded',\n 'container.transport.rail_unloaded',\n 'container.transport.transshipment_arrived',\n 'container.transport.transshipment_discharged',\n 'container.transport.transshipment_loaded',\n 'container.transport.transshipment_departed',\n 'container.transport.feeder_arrived',\n 'container.transport.feeder_discharged',\n 'container.transport.feeder_loaded',\n 'container.transport.feeder_departed',\n 'container.transport.empty_out',\n 'container.transport.full_in',\n 'container.transport.full_out',\n 'container.transport.empty_in',\n 'container.transport.vessel_berthed',\n 'shipment.estimated.arrival',\n 'tracking_request.succeeded',\n 'tracking_request.failed',\n 'tracking_request.awaiting_manifest',\n 'tracking_request.tracking_stopped',\n 'container.created',\n 'container.updated',\n 'container.pod_terminal_changed',\n 'container.transport.arrived_at_inland_destination',\n 'container.transport.estimated.arrived_at_inland_destination',\n 'container.pickup_lfd.changed'\n ]\n }\n },\n secret: {\n type: 'string',\n description: 'A random token that will sign all delivered webhooks'\n },\n url: {\n type: 'string',\n description: 'https end point'\n },\n headers: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n name: {\n type: 'string'\n },\n value: {\n type: 'string'\n }\n }\n }\n }\n },\n required: [ 'active',\n 'events',\n 'secret',\n 'url'\n ]\n }\n },\n required: [ 'id',\n 'type'\n ]\n },\n links: {\n type: 'object',\n title: 'links',\n properties: {\n first: {\n type: 'string'\n },\n last: {\n type: 'string'\n },\n next: {\n type: 'string'\n },\n prev: {\n type: 'string'\n },\n self: {\n type: 'string'\n }\n }\n },\n meta: {\n type: 'object',\n title: 'meta',\n properties: {\n size: {\n type: 'integer'\n },\n total: {\n type: 'integer'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + 'page[number]': { + type: 'integer', + }, + 'page[size]': { + type: 'integer', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.webhooks.list(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/webhooks/retrieve-webhooks.ts b/packages/mcp-server/src/tools/webhooks/retrieve-webhooks.ts new file mode 100644 index 00000000..583cee16 --- /dev/null +++ b/packages/mcp-server/src/tools/webhooks/retrieve-webhooks.ts @@ -0,0 +1,54 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'webhooks', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/webhooks/{id}', + operationId: 'get-webhooks-id', +}; + +export const tool: Tool = { + name: 'retrieve_webhooks', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet the details of a single webhook\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/webhook_retrieve_response',\n $defs: {\n webhook_retrieve_response: {\n type: 'object',\n properties: {\n data: {\n $ref: '#/$defs/webhook'\n }\n }\n },\n webhook: {\n type: 'object',\n title: 'webhook',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'webhook'\n ]\n },\n attributes: {\n type: 'object',\n properties: {\n active: {\n type: 'boolean',\n description: 'Whether the webhook will be delivered when events are triggered'\n },\n events: {\n type: 'array',\n description: 'The list of events to enabled for this endpoint',\n items: {\n type: 'string',\n enum: [ 'container.transport.vessel_arrived',\n 'container.transport.vessel_discharged',\n 'container.transport.vessel_loaded',\n 'container.transport.vessel_departed',\n 'container.transport.rail_departed',\n 'container.transport.rail_arrived',\n 'container.transport.rail_loaded',\n 'container.transport.rail_unloaded',\n 'container.transport.transshipment_arrived',\n 'container.transport.transshipment_discharged',\n 'container.transport.transshipment_loaded',\n 'container.transport.transshipment_departed',\n 'container.transport.feeder_arrived',\n 'container.transport.feeder_discharged',\n 'container.transport.feeder_loaded',\n 'container.transport.feeder_departed',\n 'container.transport.empty_out',\n 'container.transport.full_in',\n 'container.transport.full_out',\n 'container.transport.empty_in',\n 'container.transport.vessel_berthed',\n 'shipment.estimated.arrival',\n 'tracking_request.succeeded',\n 'tracking_request.failed',\n 'tracking_request.awaiting_manifest',\n 'tracking_request.tracking_stopped',\n 'container.created',\n 'container.updated',\n 'container.pod_terminal_changed',\n 'container.transport.arrived_at_inland_destination',\n 'container.transport.estimated.arrived_at_inland_destination',\n 'container.pickup_lfd.changed'\n ]\n }\n },\n secret: {\n type: 'string',\n description: 'A random token that will sign all delivered webhooks'\n },\n url: {\n type: 'string',\n description: 'https end point'\n },\n headers: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n name: {\n type: 'string'\n },\n value: {\n type: 'string'\n }\n }\n }\n }\n },\n required: [ 'active',\n 'events',\n 'secret',\n 'url'\n ]\n }\n },\n required: [ 'id',\n 'type'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.webhooks.retrieve(id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/webhooks/update-webhooks.ts b/packages/mcp-server/src/tools/webhooks/update-webhooks.ts new file mode 100644 index 00000000..3cd4cafd --- /dev/null +++ b/packages/mcp-server/src/tools/webhooks/update-webhooks.ts @@ -0,0 +1,132 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isJqError, maybeFilter } from 'terminal49-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'terminal49-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Terminal49 from 'terminal49'; + +export const metadata: Metadata = { + resource: 'webhooks', + operation: 'write', + tags: [], + httpMethod: 'patch', + httpPath: '/webhooks/{id}', + operationId: 'patch-webhooks-id', +}; + +export const tool: Tool = { + name: 'update_webhooks', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nUpdate a single webhook\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/webhook_update_response',\n $defs: {\n webhook_update_response: {\n type: 'object',\n properties: {\n data: {\n $ref: '#/$defs/webhook'\n }\n }\n },\n webhook: {\n type: 'object',\n title: 'webhook',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'webhook'\n ]\n },\n attributes: {\n type: 'object',\n properties: {\n active: {\n type: 'boolean',\n description: 'Whether the webhook will be delivered when events are triggered'\n },\n events: {\n type: 'array',\n description: 'The list of events to enabled for this endpoint',\n items: {\n type: 'string',\n enum: [ 'container.transport.vessel_arrived',\n 'container.transport.vessel_discharged',\n 'container.transport.vessel_loaded',\n 'container.transport.vessel_departed',\n 'container.transport.rail_departed',\n 'container.transport.rail_arrived',\n 'container.transport.rail_loaded',\n 'container.transport.rail_unloaded',\n 'container.transport.transshipment_arrived',\n 'container.transport.transshipment_discharged',\n 'container.transport.transshipment_loaded',\n 'container.transport.transshipment_departed',\n 'container.transport.feeder_arrived',\n 'container.transport.feeder_discharged',\n 'container.transport.feeder_loaded',\n 'container.transport.feeder_departed',\n 'container.transport.empty_out',\n 'container.transport.full_in',\n 'container.transport.full_out',\n 'container.transport.empty_in',\n 'container.transport.vessel_berthed',\n 'shipment.estimated.arrival',\n 'tracking_request.succeeded',\n 'tracking_request.failed',\n 'tracking_request.awaiting_manifest',\n 'tracking_request.tracking_stopped',\n 'container.created',\n 'container.updated',\n 'container.pod_terminal_changed',\n 'container.transport.arrived_at_inland_destination',\n 'container.transport.estimated.arrived_at_inland_destination',\n 'container.pickup_lfd.changed'\n ]\n }\n },\n secret: {\n type: 'string',\n description: 'A random token that will sign all delivered webhooks'\n },\n url: {\n type: 'string',\n description: 'https end point'\n },\n headers: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n name: {\n type: 'string'\n },\n value: {\n type: 'string'\n }\n }\n }\n }\n },\n required: [ 'active',\n 'events',\n 'secret',\n 'url'\n ]\n }\n },\n required: [ 'id',\n 'type'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + data: { + type: 'object', + properties: { + attributes: { + type: 'object', + properties: { + active: { + type: 'boolean', + }, + events: { + type: 'array', + description: 'The list of events to enable for this endpoint.', + items: { + type: 'string', + enum: [ + 'container.transport.vessel_arrived', + 'container.transport.vessel_discharged', + 'container.transport.vessel_loaded', + 'container.transport.vessel_departed', + 'container.transport.rail_departed', + 'container.transport.rail_arrived', + 'container.transport.rail_loaded', + 'container.transport.rail_unloaded', + 'container.transport.transshipment_arrived', + 'container.transport.transshipment_discharged', + 'container.transport.transshipment_loaded', + 'container.transport.transshipment_departed', + 'container.transport.feeder_arrived', + 'container.transport.feeder_discharged', + 'container.transport.feeder_loaded', + 'container.transport.feeder_departed', + 'container.transport.empty_out', + 'container.transport.full_in', + 'container.transport.full_out', + 'container.transport.empty_in', + 'container.transport.vessel_berthed', + 'shipment.estimated.arrival', + 'tracking_request.succeeded', + 'tracking_request.failed', + 'tracking_request.awaiting_manifest', + 'tracking_request.tracking_stopped', + 'container.created', + 'container.updated', + 'container.pod_terminal_changed', + 'container.transport.arrived_at_inland_destination', + 'container.transport.estimated.arrived_at_inland_destination', + 'container.pickup_lfd.changed', + ], + }, + }, + headers: { + type: 'array', + description: 'Optional custom headers to pass with each webhook invocation', + items: { + type: 'object', + properties: { + name: { + type: 'string', + description: 'The name of the header. (Please not this will be auto-capitalized) ', + }, + value: { + type: 'string', + description: 'The value to pass for the header\n', + }, + }, + }, + }, + url: { + type: 'string', + description: 'The URL of the webhook endpoint.', + }, + }, + }, + type: { + type: 'string', + enum: ['webhook'], + }, + }, + required: ['attributes', 'type'], + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id', 'data'], + }, + annotations: {}, +}; + +export const handler = async (client: Terminal49, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.webhooks.update(id, body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/tests/compat.test.ts b/packages/mcp-server/tests/compat.test.ts new file mode 100644 index 00000000..d6272f6c --- /dev/null +++ b/packages/mcp-server/tests/compat.test.ts @@ -0,0 +1,1166 @@ +import { + truncateToolNames, + removeTopLevelUnions, + removeAnyOf, + inlineRefs, + applyCompatibilityTransformations, + removeFormats, + findUsedDefs, +} from '../src/compat'; +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import { JSONSchema } from '../src/compat'; +import { Endpoint } from '../src/tools'; + +describe('truncateToolNames', () => { + it('should return original names when maxLength is 0 or negative', () => { + const names = ['tool1', 'tool2', 'tool3']; + expect(truncateToolNames(names, 0)).toEqual(new Map()); + expect(truncateToolNames(names, -1)).toEqual(new Map()); + }); + + it('should return original names when all names are shorter than maxLength', () => { + const names = ['tool1', 'tool2', 'tool3']; + expect(truncateToolNames(names, 10)).toEqual(new Map()); + }); + + it('should truncate names longer than maxLength', () => { + const names = ['very-long-tool-name', 'another-long-tool-name', 'short']; + expect(truncateToolNames(names, 10)).toEqual( + new Map([ + ['very-long-tool-name', 'very-long-'], + ['another-long-tool-name', 'another-lo'], + ]), + ); + }); + + it('should handle duplicate truncated names by appending numbers', () => { + const names = ['tool-name-a', 'tool-name-b', 'tool-name-c']; + expect(truncateToolNames(names, 8)).toEqual( + new Map([ + ['tool-name-a', 'tool-na1'], + ['tool-name-b', 'tool-na2'], + ['tool-name-c', 'tool-na3'], + ]), + ); + }); +}); + +describe('removeTopLevelUnions', () => { + const createTestTool = (overrides = {}): Tool => ({ + name: 'test-tool', + description: 'Test tool', + inputSchema: { + type: 'object', + properties: {}, + }, + ...overrides, + }); + + it('should return the original tool if it has no anyOf at the top level', () => { + const tool = createTestTool({ + inputSchema: { + type: 'object', + properties: { + foo: { type: 'string' }, + }, + }, + }); + + expect(removeTopLevelUnions(tool)).toEqual([tool]); + }); + + it('should split a tool with top-level anyOf into multiple tools', () => { + const tool = createTestTool({ + name: 'union-tool', + description: 'A tool with unions', + inputSchema: { + type: 'object', + properties: { + common: { type: 'string' }, + }, + anyOf: [ + { + title: 'first variant', + description: 'Its the first variant', + properties: { + variant1: { type: 'string' }, + }, + required: ['variant1'], + }, + { + title: 'second variant', + properties: { + variant2: { type: 'number' }, + }, + required: ['variant2'], + }, + ], + }, + }); + + const result = removeTopLevelUnions(tool); + + expect(result).toEqual([ + { + name: 'union-tool_first_variant', + description: 'Its the first variant', + inputSchema: { + type: 'object', + title: 'first variant', + description: 'Its the first variant', + properties: { + common: { type: 'string' }, + variant1: { type: 'string' }, + }, + required: ['variant1'], + }, + }, + { + name: 'union-tool_second_variant', + description: 'A tool with unions', + inputSchema: { + type: 'object', + title: 'second variant', + description: 'A tool with unions', + properties: { + common: { type: 'string' }, + variant2: { type: 'number' }, + }, + required: ['variant2'], + }, + }, + ]); + }); + + it('should handle $defs and only include those used by the variant', () => { + const tool = createTestTool({ + name: 'defs-tool', + description: 'A tool with $defs', + inputSchema: { + type: 'object', + properties: { + common: { type: 'string' }, + }, + $defs: { + def1: { type: 'string', format: 'email' }, + def2: { type: 'number', minimum: 0 }, + unused: { type: 'boolean' }, + }, + anyOf: [ + { + properties: { + email: { $ref: '#/$defs/def1' }, + }, + }, + { + properties: { + count: { $ref: '#/$defs/def2' }, + }, + }, + ], + }, + }); + + const result = removeTopLevelUnions(tool); + + expect(result).toEqual([ + { + name: 'defs-tool_variant1', + description: 'A tool with $defs', + inputSchema: { + type: 'object', + description: 'A tool with $defs', + properties: { + common: { type: 'string' }, + email: { $ref: '#/$defs/def1' }, + }, + $defs: { + def1: { type: 'string', format: 'email' }, + }, + }, + }, + { + name: 'defs-tool_variant2', + description: 'A tool with $defs', + inputSchema: { + type: 'object', + description: 'A tool with $defs', + properties: { + common: { type: 'string' }, + count: { $ref: '#/$defs/def2' }, + }, + $defs: { + def2: { type: 'number', minimum: 0 }, + }, + }, + }, + ]); + }); +}); + +describe('removeAnyOf', () => { + it('should return original schema if it has no anyOf', () => { + const schema = { + type: 'object', + properties: { + foo: { type: 'string' }, + bar: { type: 'number' }, + }, + }; + + expect(removeAnyOf(schema)).toEqual(schema); + }); + + it('should remove anyOf field and use the first variant', () => { + const schema = { + type: 'object', + properties: { + common: { type: 'string' }, + }, + anyOf: [ + { + properties: { + variant1: { type: 'string' }, + }, + required: ['variant1'], + }, + { + properties: { + variant2: { type: 'number' }, + }, + required: ['variant2'], + }, + ], + }; + + const expected = { + type: 'object', + properties: { + common: { type: 'string' }, + variant1: { type: 'string' }, + }, + required: ['variant1'], + }; + + expect(removeAnyOf(schema)).toEqual(expected); + }); + + it('should recursively remove anyOf fields from nested properties', () => { + const schema = { + type: 'object', + properties: { + foo: { type: 'string' }, + nested: { + type: 'object', + properties: { + bar: { type: 'number' }, + }, + anyOf: [ + { + properties: { + option1: { type: 'boolean' }, + }, + }, + { + properties: { + option2: { type: 'array' }, + }, + }, + ], + }, + }, + }; + + const expected = { + type: 'object', + properties: { + foo: { type: 'string' }, + nested: { + type: 'object', + properties: { + bar: { type: 'number' }, + option1: { type: 'boolean' }, + }, + }, + }, + }; + + expect(removeAnyOf(schema)).toEqual(expected); + }); + + it('should handle arrays', () => { + const schema = { + type: 'object', + properties: { + items: { + type: 'array', + items: { + anyOf: [{ type: 'string' }, { type: 'number' }], + }, + }, + }, + }; + + const expected = { + type: 'object', + properties: { + items: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + }; + + expect(removeAnyOf(schema)).toEqual(expected); + }); +}); + +describe('findUsedDefs', () => { + it('should handle circular references without stack overflow', () => { + const defs = { + person: { + type: 'object', + properties: { + name: { type: 'string' }, + friend: { $ref: '#/$defs/person' }, // Circular reference + }, + }, + }; + + const schema = { + type: 'object', + properties: { + user: { $ref: '#/$defs/person' }, + }, + }; + + // This should not throw a stack overflow error + expect(() => { + const result = findUsedDefs(schema, defs); + expect(result).toHaveProperty('person'); + }).not.toThrow(); + }); + + it('should handle indirect circular references without stack overflow', () => { + const defs = { + node: { + type: 'object', + properties: { + value: { type: 'string' }, + child: { $ref: '#/$defs/childNode' }, + }, + }, + childNode: { + type: 'object', + properties: { + value: { type: 'string' }, + parent: { $ref: '#/$defs/node' }, // Indirect circular reference + }, + }, + }; + + const schema = { + type: 'object', + properties: { + root: { $ref: '#/$defs/node' }, + }, + }; + + // This should not throw a stack overflow error + expect(() => { + const result = findUsedDefs(schema, defs); + expect(result).toHaveProperty('node'); + expect(result).toHaveProperty('childNode'); + }).not.toThrow(); + }); + + it('should find all used definitions in non-circular schemas', () => { + const defs = { + user: { + type: 'object', + properties: { + name: { type: 'string' }, + address: { $ref: '#/$defs/address' }, + }, + }, + address: { + type: 'object', + properties: { + street: { type: 'string' }, + city: { type: 'string' }, + }, + }, + unused: { + type: 'object', + properties: { + data: { type: 'string' }, + }, + }, + }; + + const schema = { + type: 'object', + properties: { + person: { $ref: '#/$defs/user' }, + }, + }; + + const result = findUsedDefs(schema, defs); + expect(result).toHaveProperty('user'); + expect(result).toHaveProperty('address'); + expect(result).not.toHaveProperty('unused'); + }); +}); + +describe('inlineRefs', () => { + it('should return the original schema if it does not contain $refs', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + }, + }; + + expect(inlineRefs(schema)).toEqual(schema); + }); + + it('should inline simple $refs', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + user: { $ref: '#/$defs/user' }, + }, + $defs: { + user: { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + }, + }, + }; + + const expected: JSONSchema = { + type: 'object', + properties: { + user: { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + }, + }, + }; + + expect(inlineRefs(schema)).toEqual(expected); + }); + + it('should inline nested $refs', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + order: { $ref: '#/$defs/order' }, + }, + $defs: { + order: { + type: 'object', + properties: { + id: { type: 'string' }, + items: { type: 'array', items: { $ref: '#/$defs/item' } }, + }, + }, + item: { + type: 'object', + properties: { + product: { type: 'string' }, + quantity: { type: 'integer' }, + }, + }, + }, + }; + + const expected: JSONSchema = { + type: 'object', + properties: { + order: { + type: 'object', + properties: { + id: { type: 'string' }, + items: { + type: 'array', + items: { + type: 'object', + properties: { + product: { type: 'string' }, + quantity: { type: 'integer' }, + }, + }, + }, + }, + }, + }, + }; + + expect(inlineRefs(schema)).toEqual(expected); + }); + + it('should handle circular references by removing the circular part', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + person: { $ref: '#/$defs/person' }, + }, + $defs: { + person: { + type: 'object', + properties: { + name: { type: 'string' }, + friend: { $ref: '#/$defs/person' }, // Circular reference + }, + }, + }, + }; + + const expected: JSONSchema = { + type: 'object', + properties: { + person: { + type: 'object', + properties: { + name: { type: 'string' }, + // friend property is removed to break the circular reference + }, + }, + }, + }; + + expect(inlineRefs(schema)).toEqual(expected); + }); + + it('should handle indirect circular references', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + node: { $ref: '#/$defs/node' }, + }, + $defs: { + node: { + type: 'object', + properties: { + value: { type: 'string' }, + child: { $ref: '#/$defs/childNode' }, + }, + }, + childNode: { + type: 'object', + properties: { + value: { type: 'string' }, + parent: { $ref: '#/$defs/node' }, // Circular reference through childNode + }, + }, + }, + }; + + const expected: JSONSchema = { + type: 'object', + properties: { + node: { + type: 'object', + properties: { + value: { type: 'string' }, + child: { + type: 'object', + properties: { + value: { type: 'string' }, + // parent property is removed to break the circular reference + }, + }, + }, + }, + }, + }; + + expect(inlineRefs(schema)).toEqual(expected); + }); + + it('should preserve other properties when inlining references', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + address: { $ref: '#/$defs/address', description: 'User address' }, + }, + $defs: { + address: { + type: 'object', + properties: { + street: { type: 'string' }, + city: { type: 'string' }, + }, + required: ['street'], + }, + }, + }; + + const expected: JSONSchema = { + type: 'object', + properties: { + address: { + type: 'object', + description: 'User address', + properties: { + street: { type: 'string' }, + city: { type: 'string' }, + }, + required: ['street'], + }, + }, + }; + + expect(inlineRefs(schema)).toEqual(expected); + }); +}); + +describe('removeFormats', () => { + it('should return original schema if formats capability is true', () => { + const schema = { + type: 'object', + properties: { + date: { type: 'string', description: 'A date field', format: 'date' }, + email: { type: 'string', description: 'An email field', format: 'email' }, + }, + }; + + expect(removeFormats(schema, true)).toEqual(schema); + }); + + it('should move format to description when formats capability is false', () => { + const schema = { + type: 'object', + properties: { + date: { type: 'string', description: 'A date field', format: 'date' }, + email: { type: 'string', description: 'An email field', format: 'email' }, + }, + }; + + const expected = { + type: 'object', + properties: { + date: { type: 'string', description: 'A date field (format: "date")' }, + email: { type: 'string', description: 'An email field (format: "email")' }, + }, + }; + + expect(removeFormats(schema, false)).toEqual(expected); + }); + + it('should handle properties without description', () => { + const schema = { + type: 'object', + properties: { + date: { type: 'string', format: 'date' }, + }, + }; + + const expected = { + type: 'object', + properties: { + date: { type: 'string', description: '(format: "date")' }, + }, + }; + + expect(removeFormats(schema, false)).toEqual(expected); + }); + + it('should handle nested properties', () => { + const schema = { + type: 'object', + properties: { + user: { + type: 'object', + properties: { + created_at: { type: 'string', description: 'Creation date', format: 'date-time' }, + }, + }, + }, + }; + + const expected = { + type: 'object', + properties: { + user: { + type: 'object', + properties: { + created_at: { type: 'string', description: 'Creation date (format: "date-time")' }, + }, + }, + }, + }; + + expect(removeFormats(schema, false)).toEqual(expected); + }); + + it('should handle arrays of objects', () => { + const schema = { + type: 'object', + properties: { + dates: { + type: 'array', + items: { + type: 'object', + properties: { + start: { type: 'string', description: 'Start date', format: 'date' }, + end: { type: 'string', description: 'End date', format: 'date' }, + }, + }, + }, + }, + }; + + const expected = { + type: 'object', + properties: { + dates: { + type: 'array', + items: { + type: 'object', + properties: { + start: { type: 'string', description: 'Start date (format: "date")' }, + end: { type: 'string', description: 'End date (format: "date")' }, + }, + }, + }, + }, + }; + + expect(removeFormats(schema, false)).toEqual(expected); + }); + + it('should handle schemas with $defs', () => { + const schema = { + type: 'object', + properties: { + date: { type: 'string', description: 'A date field', format: 'date' }, + }, + $defs: { + timestamp: { + type: 'string', + description: 'A timestamp field', + format: 'date-time', + }, + }, + }; + + const expected = { + type: 'object', + properties: { + date: { type: 'string', description: 'A date field (format: "date")' }, + }, + $defs: { + timestamp: { + type: 'string', + description: 'A timestamp field (format: "date-time")', + }, + }, + }; + + expect(removeFormats(schema, false)).toEqual(expected); + }); +}); + +describe('applyCompatibilityTransformations', () => { + const createTestTool = (name: string, overrides = {}): Tool => ({ + name, + description: 'Test tool', + inputSchema: { + type: 'object', + properties: {}, + }, + ...overrides, + }); + + const createTestEndpoint = (tool: Tool): Endpoint => ({ + tool, + handler: jest.fn(), + metadata: { + resource: 'test', + operation: 'read' as const, + tags: [], + }, + }); + + it('should not modify endpoints when all capabilities are enabled', () => { + const tool = createTestTool('test-tool'); + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + expect(transformed).toEqual(endpoints); + }); + + it('should split tools with top-level unions when topLevelUnions is disabled', () => { + const tool = createTestTool('union-tool', { + inputSchema: { + type: 'object', + properties: { + common: { type: 'string' }, + }, + anyOf: [ + { + title: 'first variant', + properties: { + variant1: { type: 'string' }, + }, + }, + { + title: 'second variant', + properties: { + variant2: { type: 'number' }, + }, + }, + ], + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + expect(transformed.length).toBe(2); + expect(transformed[0]!.tool.name).toBe('union-tool_first_variant'); + expect(transformed[1]!.tool.name).toBe('union-tool_second_variant'); + }); + + it('should handle variants without titles in removeTopLevelUnions', () => { + const tool = createTestTool('union-tool', { + inputSchema: { + type: 'object', + properties: { + common: { type: 'string' }, + }, + anyOf: [ + { + properties: { + variant1: { type: 'string' }, + }, + }, + { + properties: { + variant2: { type: 'number' }, + }, + }, + ], + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + expect(transformed.length).toBe(2); + expect(transformed[0]!.tool.name).toBe('union-tool_variant1'); + expect(transformed[1]!.tool.name).toBe('union-tool_variant2'); + }); + + it('should truncate tool names when toolNameLength is set', () => { + const tools = [ + createTestTool('very-long-tool-name-that-exceeds-limit'), + createTestTool('another-long-tool-name-to-truncate'), + createTestTool('short-name'), + ]; + + const endpoints = tools.map(createTestEndpoint); + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: 20, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + expect(transformed[0]!.tool.name).toBe('very-long-tool-name-'); + expect(transformed[1]!.tool.name).toBe('another-long-tool-na'); + expect(transformed[2]!.tool.name).toBe('short-name'); + }); + + it('should inline refs when refs capability is disabled', () => { + const tool = createTestTool('ref-tool', { + inputSchema: { + type: 'object', + properties: { + user: { $ref: '#/$defs/user' }, + }, + $defs: { + user: { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + }, + }, + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: false, + unions: true, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + const schema = transformed[0]!.tool.inputSchema as JSONSchema; + expect(schema.$defs).toBeUndefined(); + + if (schema.properties) { + expect(schema.properties['user']).toEqual({ + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + }); + } + }); + + it('should preserve external refs when inlining', () => { + const tool = createTestTool('ref-tool', { + inputSchema: { + type: 'object', + properties: { + internal: { $ref: '#/$defs/internal' }, + external: { $ref: 'https://example.com/schemas/external.json' }, + }, + $defs: { + internal: { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }, + }, + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: false, + unions: true, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + const schema = transformed[0]!.tool.inputSchema as JSONSchema; + + if (schema.properties) { + expect(schema.properties['internal']).toEqual({ + type: 'object', + properties: { + name: { type: 'string' }, + }, + }); + expect(schema.properties['external']).toEqual({ + $ref: 'https://example.com/schemas/external.json', + }); + } + }); + + it('should remove anyOf fields when unions capability is disabled', () => { + const tool = createTestTool('union-tool', { + inputSchema: { + type: 'object', + properties: { + field: { + anyOf: [{ type: 'string' }, { type: 'number' }], + }, + }, + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: true, + unions: false, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + const schema = transformed[0]!.tool.inputSchema as JSONSchema; + + if (schema.properties && schema.properties['field']) { + const field = schema.properties['field']; + expect(field.anyOf).toBeUndefined(); + expect(field.type).toBe('string'); + } + }); + + it('should correctly combine topLevelUnions and toolNameLength transformations', () => { + const tool = createTestTool('very-long-union-tool-name', { + inputSchema: { + type: 'object', + properties: { + common: { type: 'string' }, + }, + anyOf: [ + { + title: 'first variant', + properties: { + variant1: { type: 'string' }, + }, + }, + { + title: 'second variant', + properties: { + variant2: { type: 'number' }, + }, + }, + ], + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: 20, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + expect(transformed.length).toBe(2); + + // Both names should be truncated because they exceed 20 characters + expect(transformed[0]!.tool.name).toBe('very-long-union-too1'); + expect(transformed[1]!.tool.name).toBe('very-long-union-too2'); + }); + + it('should correctly combine refs and unions transformations', () => { + const tool = createTestTool('complex-tool', { + inputSchema: { + type: 'object', + properties: { + user: { $ref: '#/$defs/user' }, + }, + $defs: { + user: { + type: 'object', + properties: { + preference: { + anyOf: [{ type: 'string' }, { type: 'number' }], + }, + }, + }, + }, + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: false, + unions: false, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + const schema = transformed[0]!.tool.inputSchema as JSONSchema; + + // Refs should be inlined + expect(schema.$defs).toBeUndefined(); + + // Safely access nested properties + if (schema.properties && schema.properties['user']) { + const user = schema.properties['user']; + // User should be inlined + expect(user.type).toBe('object'); + + // AnyOf in the inlined user.preference should be removed + if (user.properties && user.properties['preference']) { + const preference = user.properties['preference']; + expect(preference.anyOf).toBeUndefined(); + expect(preference.type).toBe('string'); + } + } + }); + + it('should handle formats capability being false', () => { + const tool = createTestTool('format-tool', { + inputSchema: { + type: 'object', + properties: { + date: { type: 'string', description: 'A date', format: 'date' }, + }, + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: false, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + const schema = transformed[0]!.tool.inputSchema as JSONSchema; + + if (schema.properties && schema.properties['date']) { + const dateField = schema.properties['date']; + expect(dateField['format']).toBeUndefined(); + expect(dateField['description']).toBe('A date (format: "date")'); + } + }); +}); diff --git a/packages/mcp-server/tests/dynamic-tools.test.ts b/packages/mcp-server/tests/dynamic-tools.test.ts new file mode 100644 index 00000000..08963af8 --- /dev/null +++ b/packages/mcp-server/tests/dynamic-tools.test.ts @@ -0,0 +1,185 @@ +import { dynamicTools } from '../src/dynamic-tools'; +import { Endpoint } from '../src/tools'; + +describe('dynamicTools', () => { + const fakeClient = {} as any; + + const endpoints: Endpoint[] = [ + makeEndpoint('test_read_endpoint', 'test_resource', 'read', ['test']), + makeEndpoint('test_write_endpoint', 'test_resource', 'write', ['test']), + makeEndpoint('user_endpoint', 'user', 'read', ['user', 'admin']), + makeEndpoint('admin_endpoint', 'admin', 'write', ['admin']), + ]; + + const tools = dynamicTools(endpoints); + + const toolsMap = { + list_api_endpoints: toolOrError('list_api_endpoints'), + get_api_endpoint_schema: toolOrError('get_api_endpoint_schema'), + invoke_api_endpoint: toolOrError('invoke_api_endpoint'), + }; + + describe('list_api_endpoints', () => { + it('should return all endpoints when no search query is provided', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, {}); + const result = JSON.parse(content.content[0].text); + + expect(result.tools).toHaveLength(endpoints.length); + expect(result.tools.map((t: { name: string }) => t.name)).toContain('test_read_endpoint'); + expect(result.tools.map((t: { name: string }) => t.name)).toContain('test_write_endpoint'); + expect(result.tools.map((t: { name: string }) => t.name)).toContain('user_endpoint'); + expect(result.tools.map((t: { name: string }) => t.name)).toContain('admin_endpoint'); + }); + + it('should filter endpoints by name', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'user' }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe('user_endpoint'); + }); + + it('should filter endpoints by resource', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools.some((t: { resource: string }) => t.resource === 'admin')).toBeTruthy(); + }); + + it('should filter endpoints by tag', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools.some((t: { tags: string[] }) => t.tags.includes('admin'))).toBeTruthy(); + }); + + it('should be case insensitive in search', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'ADMIN' }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools.length).toBe(2); + result.tools.forEach((tool: { name: string; resource: string; tags: string[] }) => { + expect( + tool.name.toLowerCase().includes('admin') || + tool.resource.toLowerCase().includes('admin') || + tool.tags.some((tag: string) => tag.toLowerCase().includes('admin')), + ).toBeTruthy(); + }); + }); + + it('should filter endpoints by description', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { + search_query: 'Test endpoint for user_endpoint', + }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe('user_endpoint'); + expect(result.tools[0].description).toBe('Test endpoint for user_endpoint'); + }); + + it('should filter endpoints by partial description match', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { + search_query: 'endpoint for user', + }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe('user_endpoint'); + }); + }); + + describe('get_api_endpoint_schema', () => { + it('should return schema for existing endpoint', async () => { + const content = await toolsMap.get_api_endpoint_schema.handler(fakeClient, { + endpoint: 'test_read_endpoint', + }); + const result = JSON.parse(content.content[0].text); + + expect(result).toEqual(endpoints[0]?.tool); + }); + + it('should throw error for non-existent endpoint', async () => { + await expect( + toolsMap.get_api_endpoint_schema.handler(fakeClient, { endpoint: 'non_existent_endpoint' }), + ).rejects.toThrow('Endpoint non_existent_endpoint not found'); + }); + + it('should throw error when no endpoint provided', async () => { + await expect(toolsMap.get_api_endpoint_schema.handler(fakeClient, undefined)).rejects.toThrow( + 'No endpoint provided', + ); + }); + }); + + describe('invoke_api_endpoint', () => { + it('should successfully invoke endpoint with valid arguments', async () => { + const mockHandler = endpoints[0]?.handler as jest.Mock; + mockHandler.mockClear(); + + await toolsMap.invoke_api_endpoint.handler(fakeClient, { + endpoint_name: 'test_read_endpoint', + args: { testParam: 'test value' }, + }); + + expect(mockHandler).toHaveBeenCalledWith(fakeClient, { testParam: 'test value' }); + }); + + it('should throw error for non-existent endpoint', async () => { + await expect( + toolsMap.invoke_api_endpoint.handler(fakeClient, { + endpoint_name: 'non_existent_endpoint', + args: { testParam: 'test value' }, + }), + ).rejects.toThrow(/Endpoint non_existent_endpoint not found/); + }); + + it('should throw error when no arguments provided', async () => { + await expect(toolsMap.invoke_api_endpoint.handler(fakeClient, undefined)).rejects.toThrow( + 'No endpoint provided', + ); + }); + + it('should throw error for invalid argument schema', async () => { + await expect( + toolsMap.invoke_api_endpoint.handler(fakeClient, { + endpoint_name: 'test_read_endpoint', + args: { wrongParam: 'test value' }, // Missing required testParam + }), + ).rejects.toThrow(/Invalid arguments for endpoint/); + }); + }); + + function toolOrError(name: string) { + const tool = tools.find((tool) => tool.tool.name === name); + if (!tool) throw new Error(`Tool ${name} not found`); + return tool; + } +}); + +function makeEndpoint( + name: string, + resource: string, + operation: 'read' | 'write', + tags: string[] = [], +): Endpoint { + return { + metadata: { + resource, + operation, + tags, + }, + tool: { + name, + description: `Test endpoint for ${name}`, + inputSchema: { + type: 'object', + properties: { + testParam: { type: 'string' }, + }, + required: ['testParam'], + }, + }, + handler: jest.fn().mockResolvedValue({ success: true }), + }; +} diff --git a/packages/mcp-server/tests/options.test.ts b/packages/mcp-server/tests/options.test.ts new file mode 100644 index 00000000..4d9b60ca --- /dev/null +++ b/packages/mcp-server/tests/options.test.ts @@ -0,0 +1,540 @@ +import { parseCLIOptions, parseQueryOptions } from '../src/options'; +import { Filter } from '../src/tools'; +import { parseEmbeddedJSON } from '../src/compat'; + +// Mock process.argv +const mockArgv = (args: string[]) => { + const originalArgv = process.argv; + process.argv = ['node', 'test.js', ...args]; + return () => { + process.argv = originalArgv; + }; +}; + +describe('parseCLIOptions', () => { + it('should parse basic filter options', () => { + const cleanup = mockArgv([ + '--tool=test-tool', + '--resource=test-resource', + '--operation=read', + '--tag=test-tag', + ]); + + const result = parseCLIOptions(); + + expect(result.filters).toEqual([ + { type: 'tag', op: 'include', value: 'test-tag' }, + { type: 'resource', op: 'include', value: 'test-resource' }, + { type: 'tool', op: 'include', value: 'test-tool' }, + { type: 'operation', op: 'include', value: 'read' }, + ] as Filter[]); + + expect(result.capabilities).toEqual({}); + + expect(result.list).toBe(false); + + cleanup(); + }); + + it('should parse exclusion filters', () => { + const cleanup = mockArgv([ + '--no-tool=exclude-tool', + '--no-resource=exclude-resource', + '--no-operation=write', + '--no-tag=exclude-tag', + ]); + + const result = parseCLIOptions(); + + expect(result.filters).toEqual([ + { type: 'tag', op: 'exclude', value: 'exclude-tag' }, + { type: 'resource', op: 'exclude', value: 'exclude-resource' }, + { type: 'tool', op: 'exclude', value: 'exclude-tool' }, + { type: 'operation', op: 'exclude', value: 'write' }, + ] as Filter[]); + + expect(result.capabilities).toEqual({}); + + cleanup(); + }); + + it('should parse client presets', () => { + const cleanup = mockArgv(['--client=openai-agents']); + + const result = parseCLIOptions(); + + expect(result.client).toEqual('openai-agents'); + + cleanup(); + }); + + it('should parse individual capabilities', () => { + const cleanup = mockArgv([ + '--capability=top-level-unions', + '--capability=valid-json', + '--capability=refs', + '--capability=unions', + '--capability=tool-name-length=40', + ]); + + const result = parseCLIOptions(); + + expect(result.capabilities).toEqual({ + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + toolNameLength: 40, + }); + + cleanup(); + }); + + it('should handle list option', () => { + const cleanup = mockArgv(['--list']); + + const result = parseCLIOptions(); + + expect(result.list).toBe(true); + + cleanup(); + }); + + it('should handle multiple filters of the same type', () => { + const cleanup = mockArgv(['--tool=tool1', '--tool=tool2', '--resource=res1', '--resource=res2']); + + const result = parseCLIOptions(); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'include', value: 'res1' }, + { type: 'resource', op: 'include', value: 'res2' }, + { type: 'tool', op: 'include', value: 'tool1' }, + { type: 'tool', op: 'include', value: 'tool2' }, + ] as Filter[]); + + cleanup(); + }); + + it('should handle comma-separated values in array options', () => { + const cleanup = mockArgv([ + '--tool=tool1,tool2', + '--resource=res1,res2', + '--capability=top-level-unions,valid-json,unions', + ]); + + const result = parseCLIOptions(); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'include', value: 'res1' }, + { type: 'resource', op: 'include', value: 'res2' }, + { type: 'tool', op: 'include', value: 'tool1' }, + { type: 'tool', op: 'include', value: 'tool2' }, + ] as Filter[]); + + expect(result.capabilities).toEqual({ + topLevelUnions: true, + validJson: true, + unions: true, + }); + + cleanup(); + }); + + it('should handle invalid tool-name-length format', () => { + const cleanup = mockArgv(['--capability=tool-name-length=invalid']); + + // Mock console.error to prevent output during test + const originalError = console.error; + console.error = jest.fn(); + + expect(() => parseCLIOptions()).toThrow(); + + console.error = originalError; + cleanup(); + }); + + it('should handle unknown capability', () => { + const cleanup = mockArgv(['--capability=unknown-capability']); + + // Mock console.error to prevent output during test + const originalError = console.error; + console.error = jest.fn(); + + expect(() => parseCLIOptions()).toThrow(); + + console.error = originalError; + cleanup(); + }); +}); + +describe('parseQueryOptions', () => { + const defaultOptions = { + client: undefined, + includeDynamicTools: undefined, + includeCodeTools: undefined, + includeAllTools: undefined, + filters: [], + capabilities: { + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }, + }; + + it('should parse basic filter options from query string', () => { + const query = 'tool=test-tool&resource=test-resource&operation=read&tag=test-tag'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'include', value: 'test-resource' }, + { type: 'operation', op: 'include', value: 'read' }, + { type: 'tag', op: 'include', value: 'test-tag' }, + { type: 'tool', op: 'include', value: 'test-tool' }, + ]); + + expect(result.capabilities).toEqual({ + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }); + }); + + it('should parse exclusion filters from query string', () => { + const query = 'no_tool=exclude-tool&no_resource=exclude-resource&no_operation=write&no_tag=exclude-tag'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'exclude', value: 'exclude-resource' }, + { type: 'operation', op: 'exclude', value: 'write' }, + { type: 'tag', op: 'exclude', value: 'exclude-tag' }, + { type: 'tool', op: 'exclude', value: 'exclude-tool' }, + ]); + }); + + it('should parse client option from query string', () => { + const query = 'client=openai-agents'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.client).toBe('openai-agents'); + }); + + it('should parse client capabilities from query string', () => { + const query = 'capability=top-level-unions&capability=valid-json&capability=tool-name-length%3D40'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.capabilities).toEqual({ + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: 40, + }); + }); + + it('should parse no-capability options from query string', () => { + const query = 'no_capability=top-level-unions&no_capability=refs&no_capability=formats'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.capabilities).toEqual({ + topLevelUnions: false, + validJson: true, + refs: false, + unions: true, + formats: false, + toolNameLength: undefined, + }); + }); + + it('should parse tools options from query string', () => { + const query = 'tools=dynamic&tools=all'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.includeDynamicTools).toBe(true); + expect(result.includeAllTools).toBe(true); + }); + + it('should parse no-tools options from query string', () => { + const query = 'tools=dynamic&tools=all&no_tools=dynamic'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.includeDynamicTools).toBe(false); + expect(result.includeAllTools).toBe(true); + }); + + it('should handle array values in query string', () => { + const query = 'tool[]=tool1&tool[]=tool2&resource[]=res1&resource[]=res2'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'include', value: 'res1' }, + { type: 'resource', op: 'include', value: 'res2' }, + { type: 'tool', op: 'include', value: 'tool1' }, + { type: 'tool', op: 'include', value: 'tool2' }, + ]); + }); + + it('should merge with default options', () => { + const defaultWithFilters = { + ...defaultOptions, + filters: [{ type: 'tag' as const, op: 'include' as const, value: 'existing-tag' }], + client: 'cursor' as const, + includeDynamicTools: true, + }; + + const query = 'tool=new-tool&resource=new-resource'; + const result = parseQueryOptions(defaultWithFilters, query); + + expect(result.filters).toEqual([ + { type: 'tag', op: 'include', value: 'existing-tag' }, + { type: 'resource', op: 'include', value: 'new-resource' }, + { type: 'tool', op: 'include', value: 'new-tool' }, + ]); + + expect(result.client).toBe('cursor'); + expect(result.includeDynamicTools).toBe(true); + }); + + it('should override client from default options', () => { + const defaultWithClient = { + ...defaultOptions, + client: 'cursor' as const, + }; + + const query = 'client=openai-agents'; + const result = parseQueryOptions(defaultWithClient, query); + + expect(result.client).toBe('openai-agents'); + }); + + it('should merge capabilities with default options', () => { + const defaultWithCapabilities = { + ...defaultOptions, + capabilities: { + topLevelUnions: false, + validJson: false, + refs: true, + unions: true, + formats: true, + toolNameLength: 30, + }, + }; + + const query = 'capability=top-level-unions&no_capability=refs'; + const result = parseQueryOptions(defaultWithCapabilities, query); + + expect(result.capabilities).toEqual({ + topLevelUnions: true, + validJson: false, + refs: false, + unions: true, + formats: true, + toolNameLength: 30, + }); + }); + + it('should handle empty query string', () => { + const query = ''; + const result = parseQueryOptions(defaultOptions, query); + + expect(result).toEqual(defaultOptions); + }); + + it('should handle invalid query string gracefully', () => { + const query = 'invalid=value&operation=invalid-operation'; + + // Should throw due to Zod validation for invalid operation + expect(() => parseQueryOptions(defaultOptions, query)).toThrow(); + }); + + it('should preserve default undefined values when not specified', () => { + const defaultWithUndefined = { + ...defaultOptions, + client: undefined, + includeDynamicTools: undefined, + includeAllTools: undefined, + }; + + const query = 'tool=test-tool'; + const result = parseQueryOptions(defaultWithUndefined, query); + + expect(result.client).toBeUndefined(); + expect(result.includeDynamicTools).toBeFalsy(); + expect(result.includeAllTools).toBeFalsy(); + }); + + it('should handle complex query with mixed include and exclude filters', () => { + const query = + 'tool=include-tool&no_tool=exclude-tool&resource=include-res&no_resource=exclude-res&operation=read&tag=include-tag&no_tag=exclude-tag'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'include', value: 'include-res' }, + { type: 'operation', op: 'include', value: 'read' }, + { type: 'tag', op: 'include', value: 'include-tag' }, + { type: 'tool', op: 'include', value: 'include-tool' }, + { type: 'resource', op: 'exclude', value: 'exclude-res' }, + { type: 'tag', op: 'exclude', value: 'exclude-tag' }, + { type: 'tool', op: 'exclude', value: 'exclude-tool' }, + ]); + }); + + it('code tools are enabled on http servers with default option set', () => { + const query = 'tools=code'; + const result = parseQueryOptions({ ...defaultOptions, includeCodeTools: true }, query); + + expect(result.includeCodeTools).toBe(true); + }); + + it('code tools are prevented on http servers when no default option set', () => { + const query = 'tools=code'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.includeCodeTools).toBe(undefined); + }); + + it('code tools are prevented on http servers when default option is explicitly false', () => { + const query = 'tools=code'; + const result = parseQueryOptions({ ...defaultOptions, includeCodeTools: false }, query); + + expect(result.includeCodeTools).toBe(false); + }); +}); + +describe('parseEmbeddedJSON', () => { + it('should not change non-string values', () => { + const args = { + numberProp: 42, + booleanProp: true, + objectProp: { nested: 'value' }, + arrayProp: [1, 2, 3], + nullProp: null, + undefinedProp: undefined, + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).toBe(args); // Should return original object since no changes made + expect(result['numberProp']).toBe(42); + expect(result['booleanProp']).toBe(true); + expect(result['objectProp']).toEqual({ nested: 'value' }); + expect(result['arrayProp']).toEqual([1, 2, 3]); + expect(result['nullProp']).toBe(null); + expect(result['undefinedProp']).toBe(undefined); + }); + + it('should parse valid JSON objects in string properties', () => { + const args = { + jsonObjectString: '{"key": "value", "number": 123}', + regularString: 'not json', + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).not.toBe(args); // Should return new object since changes were made + expect(result['jsonObjectString']).toEqual({ key: 'value', number: 123 }); + expect(result['regularString']).toBe('not json'); + }); + + it('should leave invalid JSON in string properties unchanged', () => { + const args = { + invalidJson1: '{"key": value}', // Missing quotes around value + invalidJson2: '{key: "value"}', // Missing quotes around key + invalidJson3: '{"key": "value",}', // Trailing comma + invalidJson4: 'just a regular string', + emptyString: '', + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).toBe(args); // Should return original object since no changes made + expect(result['invalidJson1']).toBe('{"key": value}'); + expect(result['invalidJson2']).toBe('{key: "value"}'); + expect(result['invalidJson3']).toBe('{"key": "value",}'); + expect(result['invalidJson4']).toBe('just a regular string'); + expect(result['emptyString']).toBe(''); + }); + + it('should not parse JSON primitives in string properties', () => { + const args = { + numberString: '123', + floatString: '45.67', + negativeNumberString: '-89', + booleanTrueString: 'true', + booleanFalseString: 'false', + nullString: 'null', + jsonArrayString: '[1, 2, 3, "test"]', + regularString: 'not json', + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).toBe(args); // Should return original object since no changes made + expect(result['numberString']).toBe('123'); + expect(result['floatString']).toBe('45.67'); + expect(result['negativeNumberString']).toBe('-89'); + expect(result['booleanTrueString']).toBe('true'); + expect(result['booleanFalseString']).toBe('false'); + expect(result['nullString']).toBe('null'); + expect(result['jsonArrayString']).toBe('[1, 2, 3, "test"]'); + expect(result['regularString']).toBe('not json'); + }); + + it('should handle mixed valid objects and other JSON types', () => { + const args = { + validObject: '{"success": true}', + invalidObject: '{"missing": quote}', + validNumber: '42', + validArray: '[1, 2, 3]', + keepAsString: 'hello world', + nonString: 123, + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).not.toBe(args); // Should return new object since some changes were made + expect(result['validObject']).toEqual({ success: true }); + expect(result['invalidObject']).toBe('{"missing": quote}'); + expect(result['validNumber']).toBe('42'); // Not parsed, remains string + expect(result['validArray']).toBe('[1, 2, 3]'); // Not parsed, remains string + expect(result['keepAsString']).toBe('hello world'); + expect(result['nonString']).toBe(123); + }); + + it('should return original object when no strings are present', () => { + const args = { + number: 42, + boolean: true, + object: { key: 'value' }, + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).toBe(args); // Should return original object since no changes made + }); + + it('should return original object when all strings are invalid JSON', () => { + const args = { + string1: 'hello', + string2: 'world', + string3: 'not json at all', + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).toBe(args); // Should return original object since no changes made + }); +}); diff --git a/packages/mcp-server/tests/tools.test.ts b/packages/mcp-server/tests/tools.test.ts new file mode 100644 index 00000000..cfff24a2 --- /dev/null +++ b/packages/mcp-server/tests/tools.test.ts @@ -0,0 +1,225 @@ +import { Endpoint, Filter, Metadata, query } from '../src/tools'; + +describe('Endpoint filtering', () => { + const endpoints: Endpoint[] = [ + endpoint({ + resource: 'user', + operation: 'read', + tags: ['admin'], + toolName: 'retrieve_user', + }), + endpoint({ + resource: 'user.profile', + operation: 'write', + tags: [], + toolName: 'create_user_profile', + }), + endpoint({ + resource: 'user.profile', + operation: 'read', + tags: [], + toolName: 'get_user_profile', + }), + endpoint({ + resource: 'user.roles.permissions', + operation: 'write', + tags: ['admin', 'security'], + toolName: 'update_user_role_permissions', + }), + endpoint({ + resource: 'documents.metadata.tags', + operation: 'write', + tags: ['taxonomy', 'metadata'], + toolName: 'create_document_metadata_tags', + }), + endpoint({ + resource: 'organization.settings', + operation: 'read', + tags: ['admin', 'configuration'], + toolName: 'get_organization_settings', + }), + ]; + + const tests: { name: string; filters: Filter[]; expected: string[] }[] = [ + { + name: 'match none', + filters: [], + expected: [], + }, + + // Resource tests + { + name: 'simple resource', + filters: [{ type: 'resource', op: 'include', value: 'user' }], + expected: ['retrieve_user'], + }, + { + name: 'exclude resource', + filters: [{ type: 'resource', op: 'exclude', value: 'user' }], + expected: [ + 'create_user_profile', + 'get_user_profile', + 'update_user_role_permissions', + 'create_document_metadata_tags', + 'get_organization_settings', + ], + }, + { + name: 'resource and subresources', + filters: [{ type: 'resource', op: 'include', value: 'user*' }], + expected: ['retrieve_user', 'create_user_profile', 'get_user_profile', 'update_user_role_permissions'], + }, + { + name: 'just subresources', + filters: [{ type: 'resource', op: 'include', value: 'user.*' }], + expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], + }, + { + name: 'specific subresource', + filters: [{ type: 'resource', op: 'include', value: 'user.roles.permissions' }], + expected: ['update_user_role_permissions'], + }, + { + name: 'deep wildcard match', + filters: [{ type: 'resource', op: 'include', value: '*.*.tags' }], + expected: ['create_document_metadata_tags'], + }, + + // Operation tests + { + name: 'read operation', + filters: [{ type: 'operation', op: 'include', value: 'read' }], + expected: ['retrieve_user', 'get_user_profile', 'get_organization_settings'], + }, + { + name: 'write operation', + filters: [{ type: 'operation', op: 'include', value: 'write' }], + expected: ['create_user_profile', 'update_user_role_permissions', 'create_document_metadata_tags'], + }, + { + name: 'resource and operation combined', + filters: [ + { type: 'resource', op: 'include', value: 'user.profile' }, + { type: 'operation', op: 'exclude', value: 'write' }, + ], + expected: ['get_user_profile'], + }, + + // Tag tests + { + name: 'admin tag', + filters: [{ type: 'tag', op: 'include', value: 'admin' }], + expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], + }, + { + name: 'taxonomy tag', + filters: [{ type: 'tag', op: 'include', value: 'taxonomy' }], + expected: ['create_document_metadata_tags'], + }, + { + name: 'multiple tags (OR logic)', + filters: [ + { type: 'tag', op: 'include', value: 'admin' }, + { type: 'tag', op: 'include', value: 'security' }, + ], + expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], + }, + { + name: 'excluding a tag', + filters: [ + { type: 'tag', op: 'include', value: 'admin' }, + { type: 'tag', op: 'exclude', value: 'security' }, + ], + expected: ['retrieve_user', 'get_organization_settings'], + }, + + // Tool name tests + { + name: 'tool name match', + filters: [{ type: 'tool', op: 'include', value: 'get_organization_settings' }], + expected: ['get_organization_settings'], + }, + { + name: 'two tools match', + filters: [ + { type: 'tool', op: 'include', value: 'get_organization_settings' }, + { type: 'tool', op: 'include', value: 'create_user_profile' }, + ], + expected: ['create_user_profile', 'get_organization_settings'], + }, + { + name: 'excluding tool by name', + filters: [ + { type: 'resource', op: 'include', value: 'user*' }, + { type: 'tool', op: 'exclude', value: 'retrieve_user' }, + ], + expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], + }, + + // Complex combinations + { + name: 'complex filter: read operations with admin tag', + filters: [ + { type: 'operation', op: 'include', value: 'read' }, + { type: 'tag', op: 'include', value: 'admin' }, + ], + expected: [ + 'retrieve_user', + 'get_user_profile', + 'update_user_role_permissions', + 'get_organization_settings', + ], + }, + { + name: 'complex filter: user resources with no tags', + filters: [ + { type: 'resource', op: 'include', value: 'user.profile' }, + { type: 'tag', op: 'exclude', value: 'admin' }, + ], + expected: ['create_user_profile', 'get_user_profile'], + }, + { + name: 'complex filter: user resources and tags', + filters: [ + { type: 'resource', op: 'include', value: 'user.profile' }, + { type: 'tag', op: 'include', value: 'admin' }, + ], + expected: [ + 'retrieve_user', + 'create_user_profile', + 'get_user_profile', + 'update_user_role_permissions', + 'get_organization_settings', + ], + }, + ]; + + tests.forEach((test) => { + it(`filters by ${test.name}`, () => { + const filtered = query(test.filters, endpoints); + expect(filtered.map((e) => e.tool.name)).toEqual(test.expected); + }); + }); +}); + +function endpoint({ + resource, + operation, + tags, + toolName, +}: { + resource: string; + operation: Metadata['operation']; + tags: string[]; + toolName: string; +}): Endpoint { + return { + metadata: { + resource, + operation, + tags, + }, + tool: { name: toolName, inputSchema: { type: 'object', properties: {} } }, + handler: jest.fn(), + }; +} diff --git a/packages/mcp-server/tsc-multi.json b/packages/mcp-server/tsc-multi.json new file mode 100644 index 00000000..4facad5a --- /dev/null +++ b/packages/mcp-server/tsc-multi.json @@ -0,0 +1,7 @@ +{ + "targets": [ + { "extname": ".js", "module": "commonjs" }, + { "extname": ".mjs", "module": "esnext" } + ], + "projects": ["tsconfig.build.json"] +} diff --git a/packages/mcp-server/tsconfig.build.json b/packages/mcp-server/tsconfig.build.json new file mode 100644 index 00000000..2d2647da --- /dev/null +++ b/packages/mcp-server/tsconfig.build.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "include": ["dist/src"], + "exclude": [], + "compilerOptions": { + "rootDir": "./dist/src", + "paths": { + "terminal49-mcp/*": ["./dist/src/*"], + "terminal49-mcp": ["./dist/src/index.ts"] + }, + "noEmit": false, + "declaration": true, + "declarationMap": true, + "outDir": "dist", + "pretty": true, + "sourceMap": true + } +} diff --git a/packages/mcp-server/tsconfig.dist-src.json b/packages/mcp-server/tsconfig.dist-src.json new file mode 100644 index 00000000..e9f2d70b --- /dev/null +++ b/packages/mcp-server/tsconfig.dist-src.json @@ -0,0 +1,11 @@ +{ + // this config is included in the published src directory to prevent TS errors + // from appearing when users go to source, and VSCode opens the source .ts file + // via declaration maps + "include": ["index.ts"], + "compilerOptions": { + "target": "es2015", + "lib": ["DOM"], + "moduleResolution": "node" + } +} diff --git a/packages/mcp-server/tsconfig.json b/packages/mcp-server/tsconfig.json new file mode 100644 index 00000000..fb66e021 --- /dev/null +++ b/packages/mcp-server/tsconfig.json @@ -0,0 +1,36 @@ +{ + "include": ["src", "tests", "examples"], + "exclude": [], + "compilerOptions": { + "target": "es2020", + "lib": ["es2020"], + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "paths": { + "terminal49-mcp/*": ["./src/*"], + "terminal49-mcp": ["./src/index.ts"] + }, + "noEmit": true, + + "resolveJsonModule": true, + + "forceConsistentCasingInFileNames": true, + + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "noImplicitReturns": true, + "alwaysStrict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + + "skipLibCheck": true + } +} diff --git a/packages/mcp-server/yarn.lock b/packages/mcp-server/yarn.lock new file mode 100644 index 00000000..2bb21c66 --- /dev/null +++ b/packages/mcp-server/yarn.lock @@ -0,0 +1,3618 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.27.2.tgz#4183f9e642fd84e74e3eea7ffa93a412e3b102c9" + integrity sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.1.tgz#89de51e86bd12246003e3524704c49541b16c3e6" + integrity sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.27.1" + "@babel/helper-compilation-targets" "^7.27.1" + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helpers" "^7.27.1" + "@babel/parser" "^7.27.1" + "@babel/template" "^7.27.1" + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.27.1", "@babel/generator@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.1.tgz#862d4fad858f7208edd487c28b58144036b76230" + integrity sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w== + dependencies: + "@babel/parser" "^7.27.1" + "@babel/types" "^7.27.1" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.27.1": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz#e1663b8b71d2de948da5c4fb2a20ca4f3ec27a6f" + integrity sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.1.tgz#ffc27013038607cdba3288e692c3611c06a18aa4" + integrity sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ== + dependencies: + "@babel/template" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.1", "@babel/parser@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.2.tgz#577518bedb17a2ce4212afd052e01f7df0941127" + integrity sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw== + dependencies: + "@babel/types" "^7.27.1" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" + integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" + integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" + integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/template@^7.27.1", "@babel/template@^7.3.3": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.1.tgz#4db772902b133bbddd1c4f7a7ee47761c1b9f291" + integrity sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.27.1" + "@babel/parser" "^7.27.1" + "@babel/template" "^7.27.1" + "@babel/types" "^7.27.1" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.3.3": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" + integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cloudflare/cabidela@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@cloudflare/cabidela/-/cabidela-0.2.4.tgz#9a3e9212e636a24d796a8f16741c24885b326a1a" + integrity sha512-u/1OwwqfcMvjmUFOcb6QtFzVVGpncHJxwl254wjzp0JC5CUlBkV6r5BbRrHI5ZYJEAgu8NeeorirxngmMFPZjQ== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" + integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== + +"@humanwhocodes/config-array@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== + dependencies: + "@humanwhocodes/object-schema" "^2.0.3" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" + integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@modelcontextprotocol/sdk@^1.11.5": + version "1.17.3" + resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz#cf92354220f0183d28179e96a9bf3a8f6d3211ae" + integrity sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg== + dependencies: + ajv "^6.12.6" + content-type "^1.0.5" + cors "^2.8.5" + cross-spawn "^7.0.5" + eventsource "^3.0.2" + eventsource-parser "^3.0.0" + express "^5.0.1" + express-rate-limit "^7.5.0" + pkce-challenge "^5.0.0" + raw-body "^3.0.0" + zod "^3.23.8" + zod-to-json-schema "^3.24.1" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pkgr/core@^0.2.3": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.4.tgz#d897170a2b0ba51f78a099edccd968f7b103387c" + integrity sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@ts-morph/common@~0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.20.0.tgz#3f161996b085ba4519731e4d24c35f6cba5b80af" + integrity sha512-7uKjByfbPpwuzkstL3L5MQyuXPSKdoNG93Fmi2JoDcTf3pEP731JdRFAduRVkOs8oqxPsXKA+ScrWkdQ8t/I+Q== + dependencies: + fast-glob "^3.2.12" + minimatch "^7.4.3" + mkdirp "^2.1.6" + path-browserify "^1.0.1" + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.7.tgz#968cdc2366ec3da159f61166428ee40f370e56c2" + integrity sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng== + dependencies: + "@babel/types" "^7.20.7" + +"@types/body-parser@*": + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/cors@^2.8.19": + version "2.8.19" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342" + integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@^5.0.0": + version "5.0.7" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz#2fa94879c9d46b11a5df4c74ac75befd6b283de6" + integrity sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.3.tgz#6c4bc6acddc2e2a587142e1d8be0bce20757e956" + integrity sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/serve-static" "*" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/http-errors@*": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" + integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.4.0": + version "29.5.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/node@*": + version "22.15.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.17.tgz#355ccec95f705b664e4332bb64a7f07db30b7055" + integrity sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw== + dependencies: + undici-types "~6.21.0" + +"@types/qs@*", "@types/qs@^6.14.0": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" + integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/send@*": + version "0.17.5" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74" + integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.8.tgz#8180c3fbe4a70e8f00b9f70b9ba7f08f35987877" + integrity sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "*" + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz#62f1befe59647524994e89de4516d8dcba7a850a" + integrity sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/type-utils" "8.31.1" + "@typescript-eslint/utils" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/parser@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.31.1.tgz#e9b0ccf30d37dde724ee4d15f4dbc195995cce1b" + integrity sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q== + dependencies: + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/typescript-estree" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz#1eb52e76878f545e4add142e0d8e3e97e7aa443b" + integrity sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw== + dependencies: + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + +"@typescript-eslint/type-utils@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz#be0f438fb24b03568e282a0aed85f776409f970c" + integrity sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA== + dependencies: + "@typescript-eslint/typescript-estree" "8.31.1" + "@typescript-eslint/utils" "8.31.1" + debug "^4.3.4" + ts-api-utils "^2.0.1" + +"@typescript-eslint/types@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.31.1.tgz#478ed6f7e8aee1be7b63a60212b6bffe1423b5d4" + integrity sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ== + +"@typescript-eslint/typescript-estree@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz#37792fe7ef4d3021c7580067c8f1ae66daabacdf" + integrity sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag== + dependencies: + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/utils@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.31.1.tgz#5628ea0393598a0b2f143d0fc6d019f0dee9dd14" + integrity sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/typescript-estree" "8.31.1" + +"@typescript-eslint/visitor-keys@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz#6742b0e3ba1e0c1e35bdaf78c03e759eb8dd8e75" + integrity sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw== + dependencies: + "@typescript-eslint/types" "8.31.1" + eslint-visitor-keys "^4.2.0" + +"@ungap/structured-clone@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +"@valtown/deno-http-worker@^0.0.21": + version "0.0.21" + resolved "https://registry.yarnpkg.com/@valtown/deno-http-worker/-/deno-http-worker-0.0.21.tgz#9ce3b5c1d0db211fe7ea8297881fe551838474ad" + integrity sha512-16kFuUykann75lNytnXXIQlmpzreZjzdyT27ebT3yNGCS3kKaS1iZYWHc3Si9An54Cphwr4qEcviChQkEeJBlA== + +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: + version "8.14.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" + integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv@^6.12.4, ajv@^6.12.6: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +async@^3.2.3: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" + integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +body-parser@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.0.tgz#f7a9656de305249a715b549b7b8fd1ab9dfddcfa" + integrity sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg== + dependencies: + bytes "^3.1.2" + content-type "^1.0.5" + debug "^4.4.0" + http-errors "^2.0.0" + iconv-lite "^0.6.3" + on-finished "^2.4.1" + qs "^6.14.0" + raw-body "^3.0.0" + type-is "^2.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0: + version "4.24.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.5.tgz#aa0f5b8560fe81fde84c6dcb38f759bafba0e11b" + integrity sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw== + dependencies: + caniuse-lite "^1.0.30001716" + electron-to-chromium "^1.5.149" + node-releases "^2.0.19" + update-browserslist-db "^1.1.3" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +bytes@3.1.2, bytes@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001716: + version "1.0.30001717" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz#5d9fec5ce09796a1893013825510678928aca129" + integrity sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw== + +chalk@^4.0.0, chalk@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +code-block-writer@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-12.0.0.tgz#4dd58946eb4234105aff7f0035977b2afdc2a770" + integrity sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +content-disposition@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.0.tgz#844426cb398f934caefcbb172200126bc7ceace2" + integrity sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg== + dependencies: + safe-buffer "5.2.1" + +content-type@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie-signature@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== + +cookie@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.5: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7, debug@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +dedent@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.6.0.tgz#79d52d6389b1ffa67d2bcef59ba51847a9d503b2" + integrity sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +depd@2.0.0, depd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.5.149: + version "1.5.151" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz#5edd6c17e1b2f14b4662c41b9379f96cc8c2bb7c" + integrity sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +encodeurl@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-plugin-prettier@^5.0.1: + version "5.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz#54d4748904e58eaf1ffe26c4bffa4986ca7f952b" + integrity sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.11.0" + +eslint-plugin-unused-imports@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.2.0.tgz#63a98c9ad5f622cd9f830f70bc77739f25ccfe0d" + integrity sha512-6uXyn6xdINEpxE1MtDjxQsyXB37lfyO2yKGVVgtD7WEWQGORSOZjgrD6hBhvGv4/SO+TOlS+UnC6JppRqbuwGQ== + dependencies: + eslint-rule-composer "^0.3.0" + +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + +eslint@^8.49.0: + version "8.57.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.1" + "@humanwhocodes/config-array" "^0.13.0" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +eventsource-parser@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.3.tgz#e9af1d40b77e6268cdcbc767321e8b9f066adea8" + integrity sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA== + +eventsource-parser@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.1.tgz#5e358dba9a55ba64ca90da883c4ca35bd82467bd" + integrity sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA== + +eventsource@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-3.0.7.tgz#1157622e2f5377bb6aef2114372728ba0c156989" + integrity sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA== + dependencies: + eventsource-parser "^3.0.1" + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +express-rate-limit@^7.5.0: + version "7.5.0" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.5.0.tgz#6a67990a724b4fbbc69119419feef50c51e8b28f" + integrity sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg== + +express@^5.0.1, express@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/express/-/express-5.1.0.tgz#d31beaf715a0016f0d53f47d3b4d7acf28c75cc9" + integrity sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA== + dependencies: + accepts "^2.0.0" + body-parser "^2.2.0" + content-disposition "^1.0.0" + content-type "^1.0.5" + cookie "^0.7.1" + cookie-signature "^1.2.1" + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + finalhandler "^2.1.0" + fresh "^2.0.0" + http-errors "^2.0.0" + merge-descriptors "^2.0.0" + mime-types "^3.0.0" + on-finished "^2.4.1" + once "^1.4.0" + parseurl "^1.3.3" + proxy-addr "^2.0.7" + qs "^6.14.0" + range-parser "^1.2.1" + router "^2.2.0" + send "^1.1.0" + serve-static "^2.2.0" + statuses "^2.0.1" + type-is "^2.0.1" + vary "^1.1.2" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-glob@^3.2.12, fast-glob@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.0.tgz#72306373aa89d05a8242ed569ed86a1bff7c561f" + integrity sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q== + dependencies: + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + on-finished "^2.4.1" + parseurl "^1.3.3" + statuses "^2.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" + integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +get-stdin@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-errors@2.0.0, http-errors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.6.3, iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.4.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +"jq-web@https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz": + version "0.8.8" + resolved "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz#7849ef64bdfc28f70cbfc9888f886860e96da10d" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.1.1, make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + +merge-descriptors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" + integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-types@^3.0.0, mime-types@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce" + integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA== + dependencies: + mime-db "^1.54.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^7.4.3: + version "7.4.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.6.tgz#845d6f254d8f4a5e4fd6baf44d5f10c8448365fb" + integrity sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" + integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-assign@^4: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + +on-finished@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +once@^1.3.0, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-all@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-all/-/p-all-3.0.0.tgz#077c023c37e75e760193badab2bad3ccd5782bfb" + integrity sha512-qUZbvbBFVXm6uJ7U/WDiO0fv6waBMbjlCm4E66oZdRR+egswICarIdHyVSZZHudH8T5SF8x/JG0q0duFzPnlBw== + dependencies: + p-map "^4.0.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parseurl@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" + integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +pkce-challenge@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pkce-challenge/-/pkce-challenge-5.0.0.tgz#c3a405cb49e272094a38e890a2b51da0228c4d97" + integrity sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^3.0.0: + version "3.5.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5" + integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +proxy-addr@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +qs@^6.14.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" + integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== + dependencies: + side-channel "^1.1.0" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +range-parser@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.0.tgz#25b3476f07a51600619dae3fe82ddc28a36e5e0f" + integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.6.3" + unpipe "1.0.0" + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.20.0: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +router@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" + integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== + dependencies: + debug "^4.4.0" + depd "^2.0.0" + is-promise "^4.0.0" + parseurl "^1.3.3" + path-to-regexp "^8.0.0" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@5.2.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.7.1: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + +send@^1.1.0, send@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/send/-/send-1.2.0.tgz#32a7554fb777b831dfa828370f773a3808d37212" + integrity sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw== + dependencies: + debug "^4.3.5" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + fresh "^2.0.0" + http-errors "^2.0.0" + mime-types "^3.0.1" + ms "^2.1.3" + on-finished "^2.4.1" + range-parser "^1.2.1" + statuses "^2.0.1" + +serve-static@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.0.tgz#9c02564ee259bdd2251b82d659a2e7e1938d66f9" + integrity sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ== + dependencies: + encodeurl "^2.0.0" + escape-html "^1.0.3" + parseurl "^1.3.3" + send "^1.2.0" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +statuses@2.0.1, statuses@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-to-stream@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/string-to-stream/-/string-to-stream-3.0.1.tgz#480e6fb4d5476d31cb2221f75307a5dcb6638a42" + integrity sha512-Hl092MV3USJuUCC6mfl9sPzGloA3K5VwdIeJjYIkXY/8K+mUvaeEabWJgArp+xXrsWxCajeT2pc4axbVhIZJyg== + dependencies: + readable-stream "^3.4.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +superstruct@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" + integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +synckit@^0.11.0: + version "0.11.4" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.4.tgz#48972326b59723fc15b8d159803cf8302b545d59" + integrity sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ== + dependencies: + "@pkgr/core" "^0.2.3" + tslib "^2.8.1" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +ts-api-utils@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" + integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== + +ts-jest@^29.1.0: + version "29.3.2" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.3.2.tgz#0576cdf0a507f811fe73dcd16d135ce89f8156cb" + integrity sha512-bJJkrWc6PjFVz5g2DGCNUo8z7oFEYaz1xP1NpeDU7KNLMWPpEyV8Chbpkn8xjzgRDpQhnGMyvyldoL7h8JXyug== + dependencies: + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.7.1" + type-fest "^4.39.1" + yargs-parser "^21.1.1" + +ts-morph@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-19.0.0.tgz#43e95fb0156c3fe3c77c814ac26b7d0be2f93169" + integrity sha512-D6qcpiJdn46tUqV45vr5UGM2dnIEuTGNxVhg0sk5NX11orcouwj6i1bMqZIz2mZTZB1Hcgy7C3oEVhAT+f6mbQ== + dependencies: + "@ts-morph/common" "~0.20.0" + code-block-writer "^12.0.0" + +ts-node@^10.5.0: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz": + version "1.1.9" + resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz#777f6f5d9e26bf0e94e5170990dd3a841d6707cd" + dependencies: + debug "^4.3.7" + fast-glob "^3.3.2" + get-stdin "^8.0.0" + p-all "^3.0.0" + picocolors "^1.1.1" + signal-exit "^3.0.7" + string-to-stream "^3.0.1" + superstruct "^1.0.4" + tslib "^2.8.1" + yargs "^17.7.2" + +tsconfig-paths@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^4.39.1: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + +type-is@^2.0.0, type-is@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" + integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== + dependencies: + content-type "^1.0.5" + media-typer "^1.1.0" + mime-types "^3.0.0" + +typescript@5.8.3: + version "5.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" + integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +unpipe@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +vary@^1, vary@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zod-to-json-schema@^3.24.1, zod-to-json-schema@^3.24.5: + version "3.24.5" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz#d1095440b147fb7c2093812a53c54df8d5df50a3" + integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g== + +zod-validation-error@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.1.tgz#a105723eb40299578a6a38cb86647068f6d005b1" + integrity sha512-F3rdaCOHs5ViJ5YTz5zzRtfkQdMdIeKudJAoxy7yB/2ZMEHw73lmCAcQw11r7++20MyGl4WV59EVh7A9rNAyog== + +zod@^3.23.8: + version "3.24.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.4.tgz#e2e2cca5faaa012d76e527d0d36622e0a90c315f" + integrity sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg== + +zod@^3.25.20: + version "3.25.76" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" + integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..b1909804 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,73 @@ +{ + "packages": { + ".": {} + }, + "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", + "include-v-in-tag": true, + "include-component-in-tag": false, + "versioning": "prerelease", + "prerelease": true, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "pull-request-header": "Automated Release PR", + "pull-request-title-pattern": "release: ${version}", + "changelog-sections": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "perf", + "section": "Performance Improvements" + }, + { + "type": "revert", + "section": "Reverts" + }, + { + "type": "chore", + "section": "Chores" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "style", + "section": "Styles" + }, + { + "type": "refactor", + "section": "Refactors" + }, + { + "type": "test", + "section": "Tests", + "hidden": true + }, + { + "type": "build", + "section": "Build System" + }, + { + "type": "ci", + "section": "Continuous Integration", + "hidden": true + } + ], + "release-type": "node", + "extra-files": [ + "src/version.ts", + "README.md", + "packages/mcp-server/yarn.lock", + { + "type": "json", + "path": "packages/mcp-server/package.json", + "jsonpath": "$.version" + } + ] +} diff --git a/render.yaml b/render.yaml deleted file mode 100644 index 4e0145b9..00000000 --- a/render.yaml +++ /dev/null @@ -1,5 +0,0 @@ -services: -- type: web - name: redoc - env: docker - plan: starter diff --git a/scripts/bootstrap b/scripts/bootstrap new file mode 100755 index 00000000..a8b69ff3 --- /dev/null +++ b/scripts/bootstrap @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then + brew bundle check >/dev/null 2>&1 || { + echo -n "==> Install Homebrew dependencies? (y/N): " + read -r response + case "$response" in + [yY][eE][sS]|[yY]) + brew bundle + ;; + *) + ;; + esac + echo + } +fi + +echo "==> Installing Node dependencies…" + +PACKAGE_MANAGER=$(command -v yarn >/dev/null 2>&1 && echo "yarn" || echo "npm") + +$PACKAGE_MANAGER install "$@" diff --git a/scripts/build b/scripts/build new file mode 100755 index 00000000..f4a4c918 --- /dev/null +++ b/scripts/build @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +set -exuo pipefail + +cd "$(dirname "$0")/.." + +node scripts/utils/check-version.cjs + +# Build into dist and will publish the package from there, +# so that src/resources/foo.ts becomes /resources/foo.js +# This way importing from `"terminal49/resources/foo"` works +# even with `"moduleResolution": "node"` + +rm -rf dist; mkdir dist +# Copy src to dist/src and build from dist/src into dist, so that +# the source map for index.js.map will refer to ./src/index.ts etc +cp -rp src README.md dist +for file in LICENSE CHANGELOG.md; do + if [ -e "${file}" ]; then cp "${file}" dist; fi +done +if [ -e "bin/cli" ]; then + mkdir -p dist/bin + cp -p "bin/cli" dist/bin/; +fi +if [ -e "bin/migration-config.json" ]; then + mkdir -p dist/bin + cp -p "bin/migration-config.json" dist/bin/; +fi +# this converts the export map paths for the dist directory +# and does a few other minor things +node scripts/utils/make-dist-package-json.cjs > dist/package.json + +# build to .js/.mjs/.d.ts files +./node_modules/.bin/tsc-multi +# we need to patch index.js so that `new module.exports()` works for cjs backwards +# compat. No way to get that from index.ts because it would cause compile errors +# when building .mjs +node scripts/utils/fix-index-exports.cjs +cp tsconfig.dist-src.json dist/src/tsconfig.json + +node scripts/utils/postprocess-files.cjs + +# make sure that nothing crashes when we require the output CJS or +# import the output ESM +(cd dist && node -e 'require("terminal49")') +(cd dist && node -e 'import("terminal49")' --input-type=module) + +if [ -e ./scripts/build-deno ] +then + ./scripts/build-deno +fi +# build all sub-packages +for dir in packages/*; do + if [ -d "$dir" ]; then + (cd "$dir" && yarn install && yarn build) + fi +done diff --git a/scripts/build-all b/scripts/build-all new file mode 100755 index 00000000..4e5ac01f --- /dev/null +++ b/scripts/build-all @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# build-all is deprecated, use build instead + +bash ./scripts/build diff --git a/scripts/fast-format b/scripts/fast-format new file mode 100755 index 00000000..53721ac0 --- /dev/null +++ b/scripts/fast-format @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +set -euo pipefail + +echo "Script started with $# arguments" +echo "Arguments: $*" +echo "Script location: $(dirname "$0")" + +cd "$(dirname "$0")/.." +echo "Changed to directory: $(pwd)" + +if [ $# -eq 0 ]; then + echo "Usage: $0 [additional-formatter-args...]" + echo "The file should contain one file path per line" + exit 1 +fi + +FILE_LIST="$1" + +echo "Looking for file: $FILE_LIST" + +if [ ! -f "$FILE_LIST" ]; then + echo "Error: File '$FILE_LIST' not found" + exit 1 +fi + +echo "==> Running eslint --fix" +ESLINT_FILES="$(grep '\.ts$' "$FILE_LIST" || true)" +if ! [ -z "$ESLINT_FILES" ]; then + echo "$ESLINT_FILES" | xargs ./node_modules/.bin/eslint --cache --fix +fi + +echo "==> Running prettier --write" +# format things eslint didn't +PRETTIER_FILES="$(grep '\.\(js\|json\)$' "$FILE_LIST" || true)" +if ! [ -z "$PRETTIER_FILES" ]; then + echo "$PRETTIER_FILES" | xargs ./node_modules/.bin/prettier \ + --write --cache --cache-strategy metadata --no-error-on-unmatched-pattern \ + '!**/dist' '!**/*.ts' '!**/*.mts' '!**/*.cts' '!**/*.js' '!**/*.mjs' '!**/*.cjs' +fi diff --git a/scripts/format b/scripts/format new file mode 100755 index 00000000..7a756401 --- /dev/null +++ b/scripts/format @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +echo "==> Running eslint --fix" +./node_modules/.bin/eslint --fix . + +echo "==> Running prettier --write" +# format things eslint didn't +./node_modules/.bin/prettier --write --cache --cache-strategy metadata . '!**/dist' '!**/*.ts' '!**/*.mts' '!**/*.cts' '!**/*.js' '!**/*.mjs' '!**/*.cjs' diff --git a/scripts/lint b/scripts/lint new file mode 100755 index 00000000..3ffb78a6 --- /dev/null +++ b/scripts/lint @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +echo "==> Running eslint" +./node_modules/.bin/eslint . + +echo "==> Building" +./scripts/build + +echo "==> Checking types" +./node_modules/typescript/bin/tsc + +echo "==> Running Are The Types Wrong?" +./node_modules/.bin/attw --pack dist -f json >.attw.json || true +node scripts/utils/attw-report.cjs + +echo "==> Running publint" +./node_modules/.bin/publint dist diff --git a/scripts/mock b/scripts/mock new file mode 100755 index 00000000..0b28f6ea --- /dev/null +++ b/scripts/mock @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +if [[ -n "$1" && "$1" != '--'* ]]; then + URL="$1" + shift +else + URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" +fi + +# Check if the URL is empty +if [ -z "$URL" ]; then + echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" + exit 1 +fi + +echo "==> Starting mock server with URL ${URL}" + +# Run prism mock on the given spec +if [ "$1" == "--daemon" ]; then + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & + + # Wait for server to come online + echo -n "Waiting for server" + while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + echo -n "." + sleep 0.1 + done + + if grep -q "✖ fatal" ".prism.log"; then + cat .prism.log + exit 1 + fi + + echo +else + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" +fi diff --git a/scripts/publish-packages.ts b/scripts/publish-packages.ts new file mode 100644 index 00000000..50e93fef --- /dev/null +++ b/scripts/publish-packages.ts @@ -0,0 +1,102 @@ +/** + * Called from the `create-releases.yml` workflow with the output + * of the release please action as the first argument. + * + * Example JSON input: + * + * ```json + { + "releases_created": "true", + "release_created": "true", + "id": "137967744", + "name": "sdk: v0.14.5", + "tag_name": "sdk-v0.14.5", + "sha": "7cc2ba5c694e76a117f731d4cf0b06f8b8361f2e", + "body": "## 0.14.5 (2024-01-22)\n\n...", + "html_url": "https://github.com/$org/$repo/releases/tag/sdk-v0.14.5", + "draft": "false", + "upload_url": "https://uploads.github.com/repos/$org/$repo/releases/137967744/assets{?name,label}", + "path": ".", + "version": "0.14.5", + "major": "0", + "minor": "14", + "patch": "5", + "packages/additional-sdk--release_created": "true", + "packages/additional-sdk--id": "137967756", + "packages/additional-sdk--name": "additional-sdk: v0.5.2", + "packages/additional-sdk--tag_name": "additional-sdk-v0.5.2", + "packages/additional-sdk--sha": "7cc2ba5c694e76a117f731d4cf0b06f8b8361f2e", + "packages/additional-sdk--body": "## 0.5.2 (2024-01-22)\n\n...", + "packages/additional-sdk--html_url": "https://github.com/$org/$repo/releases/tag/additional-sdk-v0.5.2", + "packages/additional-sdk--draft": "false", + "packages/additional-sdk--upload_url": "https://uploads.github.com/repos/$org/$repo/releases/137967756/assets{?name,label}", + "packages/additional-sdk--path": "packages/additional-sdk", + "packages/additional-sdk--version": "0.5.2", + "packages/additional-sdk--major": "0", + "packages/additional-sdk--minor": "5", + "packages/additional-sdk--patch": "2", + "paths_released": "[\".\",\"packages/additional-sdk\"]" + } + ``` + */ + +import { execSync } from 'child_process'; +import path from 'path'; + +function main() { + const data = process.argv[2] ?? process.env['DATA']; + if (!data) { + throw new Error(`Usage: publish-packages.ts '{"json": "obj"}'`); + } + + const rootDir = path.join(__dirname, '..'); + console.log('root dir', rootDir); + console.log(`publish-packages called with ${data}`); + + const outputs = JSON.parse(data); + + const rawPaths = outputs.paths_released; + + if (!rawPaths) { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs to contain a truthy `paths_released` property'); + } + if (typeof rawPaths !== 'string') { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs `paths_released` property to be a JSON string'); + } + + const paths = JSON.parse(rawPaths); + if (!Array.isArray(paths)) { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs `paths_released` property to be an array'); + } + if (!paths.length) { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs `paths_released` property to contain at least one entry'); + } + + const publishScriptPath = path.join(rootDir, 'bin', 'publish-npm'); + console.log('Using publish script at', publishScriptPath); + + console.log('Ensuring root package is built'); + console.log(`$ yarn build`); + execSync(`yarn build`, { cwd: rootDir, encoding: 'utf8', stdio: 'inherit' }); + + for (const relPackagePath of paths) { + console.log('\n'); + + const packagePath = path.join(rootDir, relPackagePath); + console.log(`Publishing in directory: ${packagePath}`); + + console.log(`$ yarn install`); + execSync(`yarn install`, { cwd: packagePath, encoding: 'utf8', stdio: 'inherit' }); + + console.log(`$ bash ${publishScriptPath}`); + execSync(`bash ${publishScriptPath}`, { cwd: packagePath, encoding: 'utf8', stdio: 'inherit' }); + } + + console.log('Finished publishing packages'); +} + +main(); diff --git a/scripts/test b/scripts/test new file mode 100755 index 00000000..7bce0516 --- /dev/null +++ b/scripts/test @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +NC='\033[0m' # No Color + +function prism_is_running() { + curl --silent "http://localhost:4010" >/dev/null 2>&1 +} + +kill_server_on_port() { + pids=$(lsof -t -i tcp:"$1" || echo "") + if [ "$pids" != "" ]; then + kill "$pids" + echo "Stopped $pids." + fi +} + +function is_overriding_api_base_url() { + [ -n "$TEST_API_BASE_URL" ] +} + +if ! is_overriding_api_base_url && ! prism_is_running ; then + # When we exit this script, make sure to kill the background mock server process + trap 'kill_server_on_port 4010' EXIT + + # Start the dev server + ./scripts/mock --daemon +fi + +if is_overriding_api_base_url ; then + echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" + echo +elif ! prism_is_running ; then + echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" + echo -e "running against your OpenAPI spec." + echo + echo -e "To run the server, pass in the path or url of your OpenAPI" + echo -e "spec to the prism command:" + echo + echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" + echo + + exit 1 +else + echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" + echo +fi + +echo "==> Running tests" +./node_modules/.bin/jest "$@" diff --git a/scripts/utils/attw-report.cjs b/scripts/utils/attw-report.cjs new file mode 100644 index 00000000..b3477c0e --- /dev/null +++ b/scripts/utils/attw-report.cjs @@ -0,0 +1,24 @@ +const fs = require('fs'); +const problems = Object.values(JSON.parse(fs.readFileSync('.attw.json', 'utf-8')).problems) + .flat() + .filter( + (problem) => + !( + // This is intentional, if the user specifies .mjs they get ESM. + ( + (problem.kind === 'CJSResolvesToESM' && problem.entrypoint.endsWith('.mjs')) || + // This is intentional for backwards compat reasons. + (problem.kind === 'MissingExportEquals' && problem.implementationFileName.endsWith('/index.js')) || + // this is intentional, we deliberately attempt to import types that may not exist from parent node_modules + // folders to better support various runtimes without triggering automatic type acquisition. + (problem.kind === 'InternalResolutionError' && problem.moduleSpecifier.includes('node_modules')) + ) + ), + ); +fs.unlinkSync('.attw.json'); +if (problems.length) { + process.stdout.write('The types are wrong!\n' + JSON.stringify(problems, null, 2) + '\n'); + process.exitCode = 1; +} else { + process.stdout.write('Types ok!\n'); +} diff --git a/scripts/utils/check-is-in-git-install.sh b/scripts/utils/check-is-in-git-install.sh new file mode 100755 index 00000000..1354eb43 --- /dev/null +++ b/scripts/utils/check-is-in-git-install.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Check if you happen to call prepare for a repository that's already in node_modules. +[ "$(basename "$(dirname "$PWD")")" = 'node_modules' ] || +# The name of the containing directory that 'npm` uses, which looks like +# $HOME/.npm/_cacache/git-cloneXXXXXX +[ "$(basename "$(dirname "$PWD")")" = 'tmp' ] || +# The name of the containing directory that 'yarn` uses, which looks like +# $(yarn cache dir)/.tmp/XXXXX +[ "$(basename "$(dirname "$PWD")")" = '.tmp' ] diff --git a/scripts/utils/check-version.cjs b/scripts/utils/check-version.cjs new file mode 100644 index 00000000..86c56dfd --- /dev/null +++ b/scripts/utils/check-version.cjs @@ -0,0 +1,20 @@ +const fs = require('fs'); +const path = require('path'); + +const main = () => { + const pkg = require('../../package.json'); + const version = pkg['version']; + if (!version) throw 'The version property is not set in the package.json file'; + if (typeof version !== 'string') { + throw `Unexpected type for the package.json version field; got ${typeof version}, expected string`; + } + + const versionFile = path.resolve(__dirname, '..', '..', 'src', 'version.ts'); + const contents = fs.readFileSync(versionFile, 'utf8'); + const output = contents.replace(/(export const VERSION = ')(.*)(')/g, `$1${version}$3`); + fs.writeFileSync(versionFile, output); +}; + +if (require.main === module) { + main(); +} diff --git a/scripts/utils/fix-index-exports.cjs b/scripts/utils/fix-index-exports.cjs new file mode 100644 index 00000000..e5e10b3e --- /dev/null +++ b/scripts/utils/fix-index-exports.cjs @@ -0,0 +1,17 @@ +const fs = require('fs'); +const path = require('path'); + +const indexJs = + process.env['DIST_PATH'] ? + path.resolve(process.env['DIST_PATH'], 'index.js') + : path.resolve(__dirname, '..', '..', 'dist', 'index.js'); + +let before = fs.readFileSync(indexJs, 'utf8'); +let after = before.replace( + /^(\s*Object\.defineProperty\s*\(exports,\s*["']__esModule["'].+)$/m, + `exports = module.exports = function (...args) { + return new exports.default(...args) + } + $1`.replace(/^ /gm, ''), +); +fs.writeFileSync(indexJs, after, 'utf8'); diff --git a/scripts/utils/git-swap.sh b/scripts/utils/git-swap.sh new file mode 100755 index 00000000..79d1888e --- /dev/null +++ b/scripts/utils/git-swap.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -exuo pipefail +# the package is published to NPM from ./dist +# we want the final file structure for git installs to match the npm installs, so we + +# delete everything except ./dist and ./node_modules +find . -maxdepth 1 -mindepth 1 ! -name 'dist' ! -name 'node_modules' -exec rm -rf '{}' + + +# move everything from ./dist to . +mv dist/* . + +# delete the now-empty ./dist +rmdir dist diff --git a/scripts/utils/make-dist-package-json.cjs b/scripts/utils/make-dist-package-json.cjs new file mode 100644 index 00000000..4d6634ea --- /dev/null +++ b/scripts/utils/make-dist-package-json.cjs @@ -0,0 +1,29 @@ +const pkgJson = require(process.env['PKG_JSON_PATH'] || '../../package.json'); + +function processExportMap(m) { + for (const key in m) { + const value = m[key]; + if (typeof value === 'string') m[key] = value.replace(/^\.\/dist\//, './'); + else processExportMap(value); + } +} +processExportMap(pkgJson.exports); + +for (const key of ['types', 'main', 'module']) { + if (typeof pkgJson[key] === 'string') pkgJson[key] = pkgJson[key].replace(/^(\.\/)?dist\//, './'); +} +// Fix bin paths if present +if (pkgJson.bin) { + for (const key in pkgJson.bin) { + if (typeof pkgJson.bin[key] === 'string') { + pkgJson.bin[key] = pkgJson.bin[key].replace(/^(\.\/)?dist\//, './'); + } + } +} + +delete pkgJson.devDependencies; +delete pkgJson.scripts.prepack; +delete pkgJson.scripts.prepublishOnly; +delete pkgJson.scripts.prepare; + +console.log(JSON.stringify(pkgJson, null, 2)); diff --git a/scripts/utils/postprocess-files.cjs b/scripts/utils/postprocess-files.cjs new file mode 100644 index 00000000..deae575e --- /dev/null +++ b/scripts/utils/postprocess-files.cjs @@ -0,0 +1,94 @@ +// @ts-check +const fs = require('fs'); +const path = require('path'); + +const distDir = + process.env['DIST_PATH'] ? + path.resolve(process.env['DIST_PATH']) + : path.resolve(__dirname, '..', '..', 'dist'); + +async function* walk(dir) { + for await (const d of await fs.promises.opendir(dir)) { + const entry = path.join(dir, d.name); + if (d.isDirectory()) yield* walk(entry); + else if (d.isFile()) yield entry; + } +} + +async function postprocess() { + for await (const file of walk(distDir)) { + if (!/(\.d)?[cm]?ts$/.test(file)) continue; + + const code = await fs.promises.readFile(file, 'utf8'); + + // strip out lib="dom", types="node", and types="react" references; these + // are needed at build time, but would pollute the user's TS environment + const transformed = code.replace( + /^ *\/\/\/ * ' '.repeat(match.length - 1) + '\n', + ); + + if (transformed !== code) { + console.error(`wrote ${path.relative(process.cwd(), file)}`); + await fs.promises.writeFile(file, transformed, 'utf8'); + } + } + + const newExports = { + '.': { + require: { + types: './index.d.ts', + default: './index.js', + }, + types: './index.d.mts', + default: './index.mjs', + }, + }; + + for (const entry of await fs.promises.readdir(distDir, { withFileTypes: true })) { + if (entry.isDirectory() && entry.name !== 'src' && entry.name !== 'internal' && entry.name !== 'bin') { + const subpath = './' + entry.name; + newExports[subpath + '/*.mjs'] = { + default: subpath + '/*.mjs', + }; + newExports[subpath + '/*.js'] = { + default: subpath + '/*.js', + }; + newExports[subpath + '/*'] = { + import: subpath + '/*.mjs', + require: subpath + '/*.js', + }; + } else if (entry.isFile() && /\.[cm]?js$/.test(entry.name)) { + const { name, ext } = path.parse(entry.name); + const subpathWithoutExt = './' + name; + const subpath = './' + entry.name; + newExports[subpathWithoutExt] ||= { import: undefined, require: undefined }; + const isModule = ext[1] === 'm'; + if (isModule) { + newExports[subpathWithoutExt].import = subpath; + } else { + newExports[subpathWithoutExt].require = subpath; + } + newExports[subpath] = { + default: subpath, + }; + } + } + await fs.promises.writeFile( + 'dist/package.json', + JSON.stringify( + Object.assign( + /** @type {Record} */ ( + JSON.parse(await fs.promises.readFile('dist/package.json', 'utf-8')) + ), + { + exports: newExports, + }, + ), + null, + 2, + ), + ); +} +postprocess(); diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 00000000..5f75aa91 --- /dev/null +++ b/scripts/utils/upload-artifact.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -exuo pipefail + +RESPONSE=$(curl -X POST "$URL" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + +SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') + +if [[ "$SIGNED_URL" == "null" ]]; then + echo -e "\033[31mFailed to get signed URL.\033[0m" + exit 1 +fi + +TARBALL=$(cd dist && npm pack --silent) + +UPLOAD_RESPONSE=$(curl -v -X PUT \ + -H "Content-Type: application/gzip" \ + --data-binary "@dist/$TARBALL" "$SIGNED_URL" 2>&1) + +if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then + echo -e "\033[32mUploaded build to Stainless storage.\033[0m" + echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/terminal49-typescript/$SHA'\033[0m" +else + echo -e "\033[31mFailed to upload artifact.\033[0m" + exit 1 +fi diff --git a/src/api-promise.ts b/src/api-promise.ts new file mode 100644 index 00000000..8c775ee6 --- /dev/null +++ b/src/api-promise.ts @@ -0,0 +1,2 @@ +/** @deprecated Import from ./core/api-promise instead */ +export * from './core/api-promise'; diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 00000000..8f576d15 --- /dev/null +++ b/src/client.ts @@ -0,0 +1,952 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import type { RequestInit, RequestInfo, BodyInit } from './internal/builtin-types'; +import type { HTTPMethod, PromiseOrValue, MergedRequestInit, FinalizedRequestInit } from './internal/types'; +import { uuid4 } from './internal/utils/uuid'; +import { validatePositiveInteger, isAbsoluteURL, safeJSON } from './internal/utils/values'; +import { sleep } from './internal/utils/sleep'; +export type { Logger, LogLevel } from './internal/utils/log'; +import { castToError, isAbortError } from './internal/errors'; +import type { APIResponseProps } from './internal/parse'; +import { getPlatformHeaders } from './internal/detect-platform'; +import * as Shims from './internal/shims'; +import * as Opts from './internal/request-options'; +import { VERSION } from './version'; +import * as Errors from './core/error'; +import * as Uploads from './core/uploads'; +import * as API from './resources/index'; +import { APIPromise } from './core/api-promise'; +import { + Container, + ContainerGetRawEventsResponse, + ContainerGetTransportEventsParams, + ContainerGetTransportEventsResponse, + ContainerListParams, + ContainerListResponse, + ContainerRetrieveParams, + ContainerRetrieveResponse, + ContainerUpdateParams, + ContainerUpdateResponse, + Containers, + TransportEvent, +} from './resources/containers'; +import { MetroArea, MetroAreaRetrieveResponse, MetroAreas } from './resources/metro-areas'; +import { + LinkSelf, + Parties, + Party, + PartyCreateParams, + PartyCreateResponse, + PartyListParams, + PartyListResponse, + PartyRetrieveResponse, + PartyUpdateParams, + PartyUpdateResponse, +} from './resources/parties'; +import { Port, PortRetrieveResponse, Ports } from './resources/ports'; +import { + Links, + Meta, + Shipment, + ShipmentListParams, + ShipmentListResponse, + ShipmentResumeTrackingResponse, + ShipmentRetrieveParams, + ShipmentRetrieveResponse, + ShipmentStopTrackingResponse, + ShipmentUpdateParams, + ShipmentUpdateResponse, + Shipments, +} from './resources/shipments'; +import { + ShippingLine, + ShippingLineListResponse, + ShippingLineRetrieveResponse, + ShippingLines, +} from './resources/shipping-lines'; +import { Terminal, TerminalRetrieveResponse, Terminals } from './resources/terminals'; +import { + Account, + TrackingRequest, + TrackingRequestCreateParams, + TrackingRequestCreateResponse, + TrackingRequestListParams, + TrackingRequestListResponse, + TrackingRequestRetrieveParams, + TrackingRequestRetrieveResponse, + TrackingRequestUpdateParams, + TrackingRequestUpdateResponse, + TrackingRequests, +} from './resources/tracking-requests'; +import { + Vessel, + VesselRetrieveByIDResponse, + VesselRetrieveByImoResponse, + Vessels, +} from './resources/vessels'; +import { + EstimatedEvent, + WebhookNotification, + WebhookNotificationGetExamplesParams, + WebhookNotificationGetExamplesResponse, + WebhookNotificationListParams, + WebhookNotificationListResponse, + WebhookNotificationRetrieveParams, + WebhookNotificationRetrieveResponse, + WebhookNotifications, +} from './resources/webhook-notifications'; +import { + Webhook, + WebhookCreateParams, + WebhookCreateResponse, + WebhookListIPsResponse, + WebhookListParams, + WebhookListResponse, + WebhookRetrieveResponse, + WebhookUpdateParams, + WebhookUpdateResponse, + Webhooks, +} from './resources/webhooks'; +import { type Fetch } from './internal/builtin-types'; +import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers'; +import { FinalRequestOptions, RequestOptions } from './internal/request-options'; +import { readEnv } from './internal/utils/env'; +import { + type LogLevel, + type Logger, + formatRequestDetails, + loggerFor, + parseLogLevel, +} from './internal/utils/log'; +import { isEmptyObj } from './internal/utils/values'; + +export interface ClientOptions { + /** + * `Token YOUR_API_TOKEN` + * + * The APIs require authentication to be done using header-based API Key and Secret Authentication. + * + * API key and secret are sent va the `Authorization` request header. + * + * You send your API key and secret in the following way: + * + * `Authorization: Token YOUR_API_KEY` + */ + apiKey?: string | undefined; + + /** + * Override the default base URL for the API, e.g., "https://api.example.com/v2/" + * + * Defaults to process.env['TERMINAL49_BASE_URL']. + */ + baseURL?: string | null | undefined; + + /** + * The maximum amount of time (in milliseconds) that the client should wait for a response + * from the server before timing out a single request. + * + * Note that request timeouts are retried by default, so in a worst-case scenario you may wait + * much longer than this timeout before the promise succeeds or fails. + * + * @unit milliseconds + */ + timeout?: number | undefined; + /** + * Additional `RequestInit` options to be passed to `fetch` calls. + * Properties will be overridden by per-request `fetchOptions`. + */ + fetchOptions?: MergedRequestInit | undefined; + + /** + * Specify a custom `fetch` function implementation. + * + * If not provided, we expect that `fetch` is defined globally. + */ + fetch?: Fetch | undefined; + + /** + * The maximum number of times that the client will retry a request in case of a + * temporary failure, like a network error or a 5XX error from the server. + * + * @default 2 + */ + maxRetries?: number | undefined; + + /** + * Default headers to include with every request to the API. + * + * These can be removed in individual requests by explicitly setting the + * header to `null` in request options. + */ + defaultHeaders?: HeadersLike | undefined; + + /** + * Default query parameters to include with every request to the API. + * + * These can be removed in individual requests by explicitly setting the + * param to `undefined` in request options. + */ + defaultQuery?: Record | undefined; + + /** + * Set the log level. + * + * Defaults to process.env['TERMINAL49_LOG'] or 'warn' if it isn't set. + */ + logLevel?: LogLevel | undefined; + + /** + * Set the logger. + * + * Defaults to globalThis.console. + */ + logger?: Logger | undefined; +} + +/** + * API Client for interfacing with the Terminal49 API. + */ +export class Terminal49 { + apiKey: string; + + baseURL: string; + maxRetries: number; + timeout: number; + logger: Logger | undefined; + logLevel: LogLevel | undefined; + fetchOptions: MergedRequestInit | undefined; + + private fetch: Fetch; + #encoder: Opts.RequestEncoder; + protected idempotencyHeader?: string; + private _options: ClientOptions; + + /** + * API Client for interfacing with the Terminal49 API. + * + * @param {string | undefined} [opts.apiKey=process.env['TERMINAL49_API_KEY'] ?? undefined] + * @param {string} [opts.baseURL=process.env['TERMINAL49_BASE_URL'] ?? https://api.terminal49.com/v2] - Override the default base URL for the API. + * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. + * @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls. + * @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation. + * @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request. + * @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API. + * @param {Record} opts.defaultQuery - Default query parameters to include with every request to the API. + */ + constructor({ + baseURL = readEnv('TERMINAL49_BASE_URL'), + apiKey = readEnv('TERMINAL49_API_KEY'), + ...opts + }: ClientOptions = {}) { + if (apiKey === undefined) { + throw new Errors.Terminal49Error( + "The TERMINAL49_API_KEY environment variable is missing or empty; either provide it, or instantiate the Terminal49 client with an apiKey option, like new Terminal49({ apiKey: 'My API Key' }).", + ); + } + + const options: ClientOptions = { + apiKey, + ...opts, + baseURL: baseURL || `https://api.terminal49.com/v2`, + }; + + this.baseURL = options.baseURL!; + this.timeout = options.timeout ?? Terminal49.DEFAULT_TIMEOUT /* 1 minute */; + this.logger = options.logger ?? console; + const defaultLogLevel = 'warn'; + // Set default logLevel early so that we can log a warning in parseLogLevel. + this.logLevel = defaultLogLevel; + this.logLevel = + parseLogLevel(options.logLevel, 'ClientOptions.logLevel', this) ?? + parseLogLevel(readEnv('TERMINAL49_LOG'), "process.env['TERMINAL49_LOG']", this) ?? + defaultLogLevel; + this.fetchOptions = options.fetchOptions; + this.maxRetries = options.maxRetries ?? 2; + this.fetch = options.fetch ?? Shims.getDefaultFetch(); + this.#encoder = Opts.FallbackEncoder; + + this._options = options; + + this.apiKey = apiKey; + } + + /** + * Create a new client instance re-using the same options given to the current client with optional overriding. + */ + withOptions(options: Partial): this { + const client = new (this.constructor as any as new (props: ClientOptions) => typeof this)({ + ...this._options, + baseURL: this.baseURL, + maxRetries: this.maxRetries, + timeout: this.timeout, + logger: this.logger, + logLevel: this.logLevel, + fetch: this.fetch, + fetchOptions: this.fetchOptions, + apiKey: this.apiKey, + ...options, + }); + return client; + } + + /** + * Check whether the base URL is set to its default. + */ + #baseURLOverridden(): boolean { + return this.baseURL !== 'https://api.terminal49.com/v2'; + } + + protected defaultQuery(): Record | undefined { + return this._options.defaultQuery; + } + + protected validateHeaders({ values, nulls }: NullableHeaders) { + return; + } + + protected async authHeaders(opts: FinalRequestOptions): Promise { + return buildHeaders([{ Authorization: this.apiKey }]); + } + + /** + * Basic re-implementation of `qs.stringify` for primitive types. + */ + protected stringifyQuery(query: Record): string { + return Object.entries(query) + .filter(([_, value]) => typeof value !== 'undefined') + .map(([key, value]) => { + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; + } + if (value === null) { + return `${encodeURIComponent(key)}=`; + } + throw new Errors.Terminal49Error( + `Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`, + ); + }) + .join('&'); + } + + private getUserAgent(): string { + return `${this.constructor.name}/JS ${VERSION}`; + } + + protected defaultIdempotencyKey(): string { + return `stainless-node-retry-${uuid4()}`; + } + + protected makeStatusError( + status: number, + error: Object, + message: string | undefined, + headers: Headers, + ): Errors.APIError { + return Errors.APIError.generate(status, error, message, headers); + } + + buildURL( + path: string, + query: Record | null | undefined, + defaultBaseURL?: string | undefined, + ): string { + const baseURL = (!this.#baseURLOverridden() && defaultBaseURL) || this.baseURL; + const url = + isAbsoluteURL(path) ? + new URL(path) + : new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); + + const defaultQuery = this.defaultQuery(); + if (!isEmptyObj(defaultQuery)) { + query = { ...defaultQuery, ...query }; + } + + if (typeof query === 'object' && query && !Array.isArray(query)) { + url.search = this.stringifyQuery(query as Record); + } + + return url.toString(); + } + + /** + * Used as a callback for mutating the given `FinalRequestOptions` object. + */ + protected async prepareOptions(options: FinalRequestOptions): Promise {} + + /** + * Used as a callback for mutating the given `RequestInit` object. + * + * This is useful for cases where you want to add certain headers based off of + * the request properties, e.g. `method` or `url`. + */ + protected async prepareRequest( + request: RequestInit, + { url, options }: { url: string; options: FinalRequestOptions }, + ): Promise {} + + get(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('get', path, opts); + } + + post(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('post', path, opts); + } + + patch(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('patch', path, opts); + } + + put(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('put', path, opts); + } + + delete(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('delete', path, opts); + } + + private methodRequest( + method: HTTPMethod, + path: string, + opts?: PromiseOrValue, + ): APIPromise { + return this.request( + Promise.resolve(opts).then((opts) => { + return { method, path, ...opts }; + }), + ); + } + + request( + options: PromiseOrValue, + remainingRetries: number | null = null, + ): APIPromise { + return new APIPromise(this, this.makeRequest(options, remainingRetries, undefined)); + } + + private async makeRequest( + optionsInput: PromiseOrValue, + retriesRemaining: number | null, + retryOfRequestLogID: string | undefined, + ): Promise { + const options = await optionsInput; + const maxRetries = options.maxRetries ?? this.maxRetries; + if (retriesRemaining == null) { + retriesRemaining = maxRetries; + } + + await this.prepareOptions(options); + + const { req, url, timeout } = await this.buildRequest(options, { + retryCount: maxRetries - retriesRemaining, + }); + + await this.prepareRequest(req, { url, options }); + + /** Not an API request ID, just for correlating local log entries. */ + const requestLogID = 'log_' + ((Math.random() * (1 << 24)) | 0).toString(16).padStart(6, '0'); + const retryLogStr = retryOfRequestLogID === undefined ? '' : `, retryOf: ${retryOfRequestLogID}`; + const startTime = Date.now(); + + loggerFor(this).debug( + `[${requestLogID}] sending request`, + formatRequestDetails({ + retryOfRequestLogID, + method: options.method, + url, + options, + headers: req.headers, + }), + ); + + if (options.signal?.aborted) { + throw new Errors.APIUserAbortError(); + } + + const controller = new AbortController(); + const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError); + const headersTime = Date.now(); + + if (response instanceof globalThis.Error) { + const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; + if (options.signal?.aborted) { + throw new Errors.APIUserAbortError(); + } + // detect native connection timeout errors + // deno throws "TypeError: error sending request for url (https://example/): client error (Connect): tcp connect error: Operation timed out (os error 60): Operation timed out (os error 60)" + // undici throws "TypeError: fetch failed" with cause "ConnectTimeoutError: Connect Timeout Error (attempted address: example:443, timeout: 1ms)" + // others do not provide enough information to distinguish timeouts from other connection errors + const isTimeout = + isAbortError(response) || + /timed? ?out/i.test(String(response) + ('cause' in response ? String(response.cause) : '')); + if (retriesRemaining) { + loggerFor(this).info( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - ${retryMessage}`, + ); + loggerFor(this).debug( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (${retryMessage})`, + formatRequestDetails({ + retryOfRequestLogID, + url, + durationMs: headersTime - startTime, + message: response.message, + }), + ); + return this.retryRequest(options, retriesRemaining, retryOfRequestLogID ?? requestLogID); + } + loggerFor(this).info( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - error; no more retries left`, + ); + loggerFor(this).debug( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (error; no more retries left)`, + formatRequestDetails({ + retryOfRequestLogID, + url, + durationMs: headersTime - startTime, + message: response.message, + }), + ); + if (isTimeout) { + throw new Errors.APIConnectionTimeoutError(); + } + throw new Errors.APIConnectionError({ cause: response }); + } + + const responseInfo = `[${requestLogID}${retryLogStr}] ${req.method} ${url} ${ + response.ok ? 'succeeded' : 'failed' + } with status ${response.status} in ${headersTime - startTime}ms`; + + if (!response.ok) { + const shouldRetry = await this.shouldRetry(response); + if (retriesRemaining && shouldRetry) { + const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; + + // We don't need the body of this response. + await Shims.CancelReadableStream(response.body); + loggerFor(this).info(`${responseInfo} - ${retryMessage}`); + loggerFor(this).debug( + `[${requestLogID}] response error (${retryMessage})`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + headers: response.headers, + durationMs: headersTime - startTime, + }), + ); + return this.retryRequest( + options, + retriesRemaining, + retryOfRequestLogID ?? requestLogID, + response.headers, + ); + } + + const retryMessage = shouldRetry ? `error; no more retries left` : `error; not retryable`; + + loggerFor(this).info(`${responseInfo} - ${retryMessage}`); + + const errText = await response.text().catch((err: any) => castToError(err).message); + const errJSON = safeJSON(errText); + const errMessage = errJSON ? undefined : errText; + + loggerFor(this).debug( + `[${requestLogID}] response error (${retryMessage})`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + headers: response.headers, + message: errMessage, + durationMs: Date.now() - startTime, + }), + ); + + const err = this.makeStatusError(response.status, errJSON, errMessage, response.headers); + throw err; + } + + loggerFor(this).info(responseInfo); + loggerFor(this).debug( + `[${requestLogID}] response start`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + headers: response.headers, + durationMs: headersTime - startTime, + }), + ); + + return { response, options, controller, requestLogID, retryOfRequestLogID, startTime }; + } + + async fetchWithTimeout( + url: RequestInfo, + init: RequestInit | undefined, + ms: number, + controller: AbortController, + ): Promise { + const { signal, method, ...options } = init || {}; + if (signal) signal.addEventListener('abort', () => controller.abort()); + + const timeout = setTimeout(() => controller.abort(), ms); + + const isReadableBody = + ((globalThis as any).ReadableStream && options.body instanceof (globalThis as any).ReadableStream) || + (typeof options.body === 'object' && options.body !== null && Symbol.asyncIterator in options.body); + + const fetchOptions: RequestInit = { + signal: controller.signal as any, + ...(isReadableBody ? { duplex: 'half' } : {}), + method: 'GET', + ...options, + }; + if (method) { + // Custom methods like 'patch' need to be uppercased + // See https://github.com/nodejs/undici/issues/2294 + fetchOptions.method = method.toUpperCase(); + } + + try { + // use undefined this binding; fetch errors if bound to something else in browser/cloudflare + return await this.fetch.call(undefined, url, fetchOptions); + } finally { + clearTimeout(timeout); + } + } + + private async shouldRetry(response: Response): Promise { + // Note this is not a standard header. + const shouldRetryHeader = response.headers.get('x-should-retry'); + + // If the server explicitly says whether or not to retry, obey. + if (shouldRetryHeader === 'true') return true; + if (shouldRetryHeader === 'false') return false; + + // Retry on request timeouts. + if (response.status === 408) return true; + + // Retry on lock timeouts. + if (response.status === 409) return true; + + // Retry on rate limits. + if (response.status === 429) return true; + + // Retry internal errors. + if (response.status >= 500) return true; + + return false; + } + + private async retryRequest( + options: FinalRequestOptions, + retriesRemaining: number, + requestLogID: string, + responseHeaders?: Headers | undefined, + ): Promise { + let timeoutMillis: number | undefined; + + // Note the `retry-after-ms` header may not be standard, but is a good idea and we'd like proactive support for it. + const retryAfterMillisHeader = responseHeaders?.get('retry-after-ms'); + if (retryAfterMillisHeader) { + const timeoutMs = parseFloat(retryAfterMillisHeader); + if (!Number.isNaN(timeoutMs)) { + timeoutMillis = timeoutMs; + } + } + + // About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After + const retryAfterHeader = responseHeaders?.get('retry-after'); + if (retryAfterHeader && !timeoutMillis) { + const timeoutSeconds = parseFloat(retryAfterHeader); + if (!Number.isNaN(timeoutSeconds)) { + timeoutMillis = timeoutSeconds * 1000; + } else { + timeoutMillis = Date.parse(retryAfterHeader) - Date.now(); + } + } + + // If the API asks us to wait a certain amount of time (and it's a reasonable amount), + // just do what it says, but otherwise calculate a default + if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) { + const maxRetries = options.maxRetries ?? this.maxRetries; + timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries); + } + await sleep(timeoutMillis); + + return this.makeRequest(options, retriesRemaining - 1, requestLogID); + } + + private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number { + const initialRetryDelay = 0.5; + const maxRetryDelay = 8.0; + + const numRetries = maxRetries - retriesRemaining; + + // Apply exponential backoff, but not more than the max. + const sleepSeconds = Math.min(initialRetryDelay * Math.pow(2, numRetries), maxRetryDelay); + + // Apply some jitter, take up to at most 25 percent of the retry time. + const jitter = 1 - Math.random() * 0.25; + + return sleepSeconds * jitter * 1000; + } + + async buildRequest( + inputOptions: FinalRequestOptions, + { retryCount = 0 }: { retryCount?: number } = {}, + ): Promise<{ req: FinalizedRequestInit; url: string; timeout: number }> { + const options = { ...inputOptions }; + const { method, path, query, defaultBaseURL } = options; + + const url = this.buildURL(path!, query as Record, defaultBaseURL); + if ('timeout' in options) validatePositiveInteger('timeout', options.timeout); + options.timeout = options.timeout ?? this.timeout; + const { bodyHeaders, body } = this.buildBody({ options }); + const reqHeaders = await this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount }); + + const req: FinalizedRequestInit = { + method, + headers: reqHeaders, + ...(options.signal && { signal: options.signal }), + ...((globalThis as any).ReadableStream && + body instanceof (globalThis as any).ReadableStream && { duplex: 'half' }), + ...(body && { body }), + ...((this.fetchOptions as any) ?? {}), + ...((options.fetchOptions as any) ?? {}), + }; + + return { req, url, timeout: options.timeout }; + } + + private async buildHeaders({ + options, + method, + bodyHeaders, + retryCount, + }: { + options: FinalRequestOptions; + method: HTTPMethod; + bodyHeaders: HeadersLike; + retryCount: number; + }): Promise { + let idempotencyHeaders: HeadersLike = {}; + if (this.idempotencyHeader && method !== 'get') { + if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey(); + idempotencyHeaders[this.idempotencyHeader] = options.idempotencyKey; + } + + const headers = buildHeaders([ + idempotencyHeaders, + { + Accept: 'application/json', + 'User-Agent': this.getUserAgent(), + 'X-Stainless-Retry-Count': String(retryCount), + ...(options.timeout ? { 'X-Stainless-Timeout': String(Math.trunc(options.timeout / 1000)) } : {}), + ...getPlatformHeaders(), + }, + await this.authHeaders(options), + this._options.defaultHeaders, + bodyHeaders, + options.headers, + ]); + + this.validateHeaders(headers); + + return headers.values; + } + + private buildBody({ options: { body, headers: rawHeaders } }: { options: FinalRequestOptions }): { + bodyHeaders: HeadersLike; + body: BodyInit | undefined; + } { + if (!body) { + return { bodyHeaders: undefined, body: undefined }; + } + const headers = buildHeaders([rawHeaders]); + if ( + // Pass raw type verbatim + ArrayBuffer.isView(body) || + body instanceof ArrayBuffer || + body instanceof DataView || + (typeof body === 'string' && + // Preserve legacy string encoding behavior for now + headers.values.has('content-type')) || + // `Blob` is superset of `File` + ((globalThis as any).Blob && body instanceof (globalThis as any).Blob) || + // `FormData` -> `multipart/form-data` + body instanceof FormData || + // `URLSearchParams` -> `application/x-www-form-urlencoded` + body instanceof URLSearchParams || + // Send chunked stream (each chunk has own `length`) + ((globalThis as any).ReadableStream && body instanceof (globalThis as any).ReadableStream) + ) { + return { bodyHeaders: undefined, body: body as BodyInit }; + } else if ( + typeof body === 'object' && + (Symbol.asyncIterator in body || + (Symbol.iterator in body && 'next' in body && typeof body.next === 'function')) + ) { + return { bodyHeaders: undefined, body: Shims.ReadableStreamFrom(body as AsyncIterable) }; + } else { + return this.#encoder({ body, headers }); + } + } + + static Terminal49 = this; + static DEFAULT_TIMEOUT = 60000; // 1 minute + + static Terminal49Error = Errors.Terminal49Error; + static APIError = Errors.APIError; + static APIConnectionError = Errors.APIConnectionError; + static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError; + static APIUserAbortError = Errors.APIUserAbortError; + static NotFoundError = Errors.NotFoundError; + static ConflictError = Errors.ConflictError; + static RateLimitError = Errors.RateLimitError; + static BadRequestError = Errors.BadRequestError; + static AuthenticationError = Errors.AuthenticationError; + static InternalServerError = Errors.InternalServerError; + static PermissionDeniedError = Errors.PermissionDeniedError; + static UnprocessableEntityError = Errors.UnprocessableEntityError; + + static toFile = Uploads.toFile; + + shipments: API.Shipments = new API.Shipments(this); + trackingRequests: API.TrackingRequests = new API.TrackingRequests(this); + webhooks: API.Webhooks = new API.Webhooks(this); + webhookNotifications: API.WebhookNotifications = new API.WebhookNotifications(this); + containers: API.Containers = new API.Containers(this); + shippingLines: API.ShippingLines = new API.ShippingLines(this); + metroAreas: API.MetroAreas = new API.MetroAreas(this); + ports: API.Ports = new API.Ports(this); + vessels: API.Vessels = new API.Vessels(this); + terminals: API.Terminals = new API.Terminals(this); + parties: API.Parties = new API.Parties(this); +} + +Terminal49.Shipments = Shipments; +Terminal49.TrackingRequests = TrackingRequests; +Terminal49.Webhooks = Webhooks; +Terminal49.WebhookNotifications = WebhookNotifications; +Terminal49.Containers = Containers; +Terminal49.ShippingLines = ShippingLines; +Terminal49.MetroAreas = MetroAreas; +Terminal49.Ports = Ports; +Terminal49.Vessels = Vessels; +Terminal49.Terminals = Terminals; +Terminal49.Parties = Parties; + +export declare namespace Terminal49 { + export type RequestOptions = Opts.RequestOptions; + + export { + Shipments as Shipments, + type Links as Links, + type Meta as Meta, + type Shipment as Shipment, + type ShipmentRetrieveResponse as ShipmentRetrieveResponse, + type ShipmentUpdateResponse as ShipmentUpdateResponse, + type ShipmentListResponse as ShipmentListResponse, + type ShipmentResumeTrackingResponse as ShipmentResumeTrackingResponse, + type ShipmentStopTrackingResponse as ShipmentStopTrackingResponse, + type ShipmentRetrieveParams as ShipmentRetrieveParams, + type ShipmentUpdateParams as ShipmentUpdateParams, + type ShipmentListParams as ShipmentListParams, + }; + + export { + TrackingRequests as TrackingRequests, + type Account as Account, + type TrackingRequest as TrackingRequest, + type TrackingRequestCreateResponse as TrackingRequestCreateResponse, + type TrackingRequestRetrieveResponse as TrackingRequestRetrieveResponse, + type TrackingRequestUpdateResponse as TrackingRequestUpdateResponse, + type TrackingRequestListResponse as TrackingRequestListResponse, + type TrackingRequestCreateParams as TrackingRequestCreateParams, + type TrackingRequestRetrieveParams as TrackingRequestRetrieveParams, + type TrackingRequestUpdateParams as TrackingRequestUpdateParams, + type TrackingRequestListParams as TrackingRequestListParams, + }; + + export { + Webhooks as Webhooks, + type Webhook as Webhook, + type WebhookCreateResponse as WebhookCreateResponse, + type WebhookRetrieveResponse as WebhookRetrieveResponse, + type WebhookUpdateResponse as WebhookUpdateResponse, + type WebhookListResponse as WebhookListResponse, + type WebhookListIPsResponse as WebhookListIPsResponse, + type WebhookCreateParams as WebhookCreateParams, + type WebhookUpdateParams as WebhookUpdateParams, + type WebhookListParams as WebhookListParams, + }; + + export { + WebhookNotifications as WebhookNotifications, + type EstimatedEvent as EstimatedEvent, + type WebhookNotification as WebhookNotification, + type WebhookNotificationRetrieveResponse as WebhookNotificationRetrieveResponse, + type WebhookNotificationListResponse as WebhookNotificationListResponse, + type WebhookNotificationGetExamplesResponse as WebhookNotificationGetExamplesResponse, + type WebhookNotificationRetrieveParams as WebhookNotificationRetrieveParams, + type WebhookNotificationListParams as WebhookNotificationListParams, + type WebhookNotificationGetExamplesParams as WebhookNotificationGetExamplesParams, + }; + + export { + Containers as Containers, + type Container as Container, + type TransportEvent as TransportEvent, + type ContainerRetrieveResponse as ContainerRetrieveResponse, + type ContainerUpdateResponse as ContainerUpdateResponse, + type ContainerListResponse as ContainerListResponse, + type ContainerGetRawEventsResponse as ContainerGetRawEventsResponse, + type ContainerGetTransportEventsResponse as ContainerGetTransportEventsResponse, + type ContainerRetrieveParams as ContainerRetrieveParams, + type ContainerUpdateParams as ContainerUpdateParams, + type ContainerListParams as ContainerListParams, + type ContainerGetTransportEventsParams as ContainerGetTransportEventsParams, + }; + + export { + ShippingLines as ShippingLines, + type ShippingLine as ShippingLine, + type ShippingLineRetrieveResponse as ShippingLineRetrieveResponse, + type ShippingLineListResponse as ShippingLineListResponse, + }; + + export { + MetroAreas as MetroAreas, + type MetroArea as MetroArea, + type MetroAreaRetrieveResponse as MetroAreaRetrieveResponse, + }; + + export { Ports as Ports, type Port as Port, type PortRetrieveResponse as PortRetrieveResponse }; + + export { + Vessels as Vessels, + type Vessel as Vessel, + type VesselRetrieveByIDResponse as VesselRetrieveByIDResponse, + type VesselRetrieveByImoResponse as VesselRetrieveByImoResponse, + }; + + export { + Terminals as Terminals, + type Terminal as Terminal, + type TerminalRetrieveResponse as TerminalRetrieveResponse, + }; + + export { + Parties as Parties, + type LinkSelf as LinkSelf, + type Party as Party, + type PartyCreateResponse as PartyCreateResponse, + type PartyRetrieveResponse as PartyRetrieveResponse, + type PartyUpdateResponse as PartyUpdateResponse, + type PartyListResponse as PartyListResponse, + type PartyCreateParams as PartyCreateParams, + type PartyUpdateParams as PartyUpdateParams, + type PartyListParams as PartyListParams, + }; +} diff --git a/src/core/README.md b/src/core/README.md new file mode 100644 index 00000000..485fce86 --- /dev/null +++ b/src/core/README.md @@ -0,0 +1,3 @@ +# `core` + +This directory holds public modules implementing non-resource-specific SDK functionality. diff --git a/src/core/api-promise.ts b/src/core/api-promise.ts new file mode 100644 index 00000000..7e2406f4 --- /dev/null +++ b/src/core/api-promise.ts @@ -0,0 +1,92 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { type Terminal49 } from '../client'; + +import { type PromiseOrValue } from '../internal/types'; +import { APIResponseProps, defaultParseResponse } from '../internal/parse'; + +/** + * A subclass of `Promise` providing additional helper methods + * for interacting with the SDK. + */ +export class APIPromise extends Promise { + private parsedPromise: Promise | undefined; + #client: Terminal49; + + constructor( + client: Terminal49, + private responsePromise: Promise, + private parseResponse: ( + client: Terminal49, + props: APIResponseProps, + ) => PromiseOrValue = defaultParseResponse, + ) { + super((resolve) => { + // this is maybe a bit weird but this has to be a no-op to not implicitly + // parse the response body; instead .then, .catch, .finally are overridden + // to parse the response + resolve(null as any); + }); + this.#client = client; + } + + _thenUnwrap(transform: (data: T, props: APIResponseProps) => U): APIPromise { + return new APIPromise(this.#client, this.responsePromise, async (client, props) => + transform(await this.parseResponse(client, props), props), + ); + } + + /** + * Gets the raw `Response` instance instead of parsing the response + * data. + * + * If you want to parse the response body but still get the `Response` + * instance, you can use {@link withResponse()}. + * + * 👋 Getting the wrong TypeScript type for `Response`? + * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` + * to your `tsconfig.json`. + */ + asResponse(): Promise { + return this.responsePromise.then((p) => p.response); + } + + /** + * Gets the parsed response data and the raw `Response` instance. + * + * If you just want to get the raw `Response` instance without parsing it, + * you can use {@link asResponse()}. + * + * 👋 Getting the wrong TypeScript type for `Response`? + * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` + * to your `tsconfig.json`. + */ + async withResponse(): Promise<{ data: T; response: Response }> { + const [data, response] = await Promise.all([this.parse(), this.asResponse()]); + return { data, response }; + } + + private parse(): Promise { + if (!this.parsedPromise) { + this.parsedPromise = this.responsePromise.then((data) => this.parseResponse(this.#client, data)); + } + return this.parsedPromise; + } + + override then( + onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null, + ): Promise { + return this.parse().then(onfulfilled, onrejected); + } + + override catch( + onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null, + ): Promise { + return this.parse().catch(onrejected); + } + + override finally(onfinally?: (() => void) | undefined | null): Promise { + return this.parse().finally(onfinally); + } +} diff --git a/src/core/error.ts b/src/core/error.ts new file mode 100644 index 00000000..1ff1fdc7 --- /dev/null +++ b/src/core/error.ts @@ -0,0 +1,130 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { castToError } from '../internal/errors'; + +export class Terminal49Error extends Error {} + +export class APIError< + TStatus extends number | undefined = number | undefined, + THeaders extends Headers | undefined = Headers | undefined, + TError extends Object | undefined = Object | undefined, +> extends Terminal49Error { + /** HTTP status for the response that caused the error */ + readonly status: TStatus; + /** HTTP headers for the response that caused the error */ + readonly headers: THeaders; + /** JSON body of the response that caused the error */ + readonly error: TError; + + constructor(status: TStatus, error: TError, message: string | undefined, headers: THeaders) { + super(`${APIError.makeMessage(status, error, message)}`); + this.status = status; + this.headers = headers; + this.error = error; + } + + private static makeMessage(status: number | undefined, error: any, message: string | undefined) { + const msg = + error?.message ? + typeof error.message === 'string' ? + error.message + : JSON.stringify(error.message) + : error ? JSON.stringify(error) + : message; + + if (status && msg) { + return `${status} ${msg}`; + } + if (status) { + return `${status} status code (no body)`; + } + if (msg) { + return msg; + } + return '(no status code or body)'; + } + + static generate( + status: number | undefined, + errorResponse: Object | undefined, + message: string | undefined, + headers: Headers | undefined, + ): APIError { + if (!status || !headers) { + return new APIConnectionError({ message, cause: castToError(errorResponse) }); + } + + const error = errorResponse as Record; + + if (status === 400) { + return new BadRequestError(status, error, message, headers); + } + + if (status === 401) { + return new AuthenticationError(status, error, message, headers); + } + + if (status === 403) { + return new PermissionDeniedError(status, error, message, headers); + } + + if (status === 404) { + return new NotFoundError(status, error, message, headers); + } + + if (status === 409) { + return new ConflictError(status, error, message, headers); + } + + if (status === 422) { + return new UnprocessableEntityError(status, error, message, headers); + } + + if (status === 429) { + return new RateLimitError(status, error, message, headers); + } + + if (status >= 500) { + return new InternalServerError(status, error, message, headers); + } + + return new APIError(status, error, message, headers); + } +} + +export class APIUserAbortError extends APIError { + constructor({ message }: { message?: string } = {}) { + super(undefined, undefined, message || 'Request was aborted.', undefined); + } +} + +export class APIConnectionError extends APIError { + constructor({ message, cause }: { message?: string | undefined; cause?: Error | undefined }) { + super(undefined, undefined, message || 'Connection error.', undefined); + // in some environments the 'cause' property is already declared + // @ts-ignore + if (cause) this.cause = cause; + } +} + +export class APIConnectionTimeoutError extends APIConnectionError { + constructor({ message }: { message?: string } = {}) { + super({ message: message ?? 'Request timed out.' }); + } +} + +export class BadRequestError extends APIError<400, Headers> {} + +export class AuthenticationError extends APIError<401, Headers> {} + +export class PermissionDeniedError extends APIError<403, Headers> {} + +export class NotFoundError extends APIError<404, Headers> {} + +export class ConflictError extends APIError<409, Headers> {} + +export class UnprocessableEntityError extends APIError<422, Headers> {} + +export class RateLimitError extends APIError<429, Headers> {} + +export class InternalServerError extends APIError {} diff --git a/src/core/resource.ts b/src/core/resource.ts new file mode 100644 index 00000000..5cb20815 --- /dev/null +++ b/src/core/resource.ts @@ -0,0 +1,11 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import type { Terminal49 } from '../client'; + +export abstract class APIResource { + protected _client: Terminal49; + + constructor(client: Terminal49) { + this._client = client; + } +} diff --git a/src/core/uploads.ts b/src/core/uploads.ts new file mode 100644 index 00000000..2882ca6d --- /dev/null +++ b/src/core/uploads.ts @@ -0,0 +1,2 @@ +export { type Uploadable } from '../internal/uploads'; +export { toFile, type ToFileInput } from '../internal/to-file'; diff --git a/src/error.ts b/src/error.ts new file mode 100644 index 00000000..fc55f46c --- /dev/null +++ b/src/error.ts @@ -0,0 +1,2 @@ +/** @deprecated Import from ./core/error instead */ +export * from './core/error'; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..bdb2570c --- /dev/null +++ b/src/index.ts @@ -0,0 +1,22 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export { Terminal49 as default } from './client'; + +export { type Uploadable, toFile } from './core/uploads'; +export { APIPromise } from './core/api-promise'; +export { Terminal49, type ClientOptions } from './client'; +export { + Terminal49Error, + APIError, + APIConnectionError, + APIConnectionTimeoutError, + APIUserAbortError, + NotFoundError, + ConflictError, + RateLimitError, + BadRequestError, + AuthenticationError, + InternalServerError, + PermissionDeniedError, + UnprocessableEntityError, +} from './core/error'; diff --git a/src/internal/README.md b/src/internal/README.md new file mode 100644 index 00000000..3ef5a25b --- /dev/null +++ b/src/internal/README.md @@ -0,0 +1,3 @@ +# `internal` + +The modules in this directory are not importable outside this package and will change between releases. diff --git a/src/internal/builtin-types.ts b/src/internal/builtin-types.ts new file mode 100644 index 00000000..c23d3bde --- /dev/null +++ b/src/internal/builtin-types.ts @@ -0,0 +1,93 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export type Fetch = (input: string | URL | Request, init?: RequestInit) => Promise; + +/** + * An alias to the builtin `RequestInit` type so we can + * easily alias it in import statements if there are name clashes. + * + * https://developer.mozilla.org/docs/Web/API/RequestInit + */ +type _RequestInit = RequestInit; + +/** + * An alias to the builtin `Response` type so we can + * easily alias it in import statements if there are name clashes. + * + * https://developer.mozilla.org/docs/Web/API/Response + */ +type _Response = Response; + +/** + * The type for the first argument to `fetch`. + * + * https://developer.mozilla.org/docs/Web/API/Window/fetch#resource + */ +type _RequestInfo = Request | URL | string; + +/** + * The type for constructing `RequestInit` Headers. + * + * https://developer.mozilla.org/docs/Web/API/RequestInit#setting_headers + */ +type _HeadersInit = RequestInit['headers']; + +/** + * The type for constructing `RequestInit` body. + * + * https://developer.mozilla.org/docs/Web/API/RequestInit#body + */ +type _BodyInit = RequestInit['body']; + +/** + * An alias to the builtin `Array` type so we can + * easily alias it in import statements if there are name clashes. + */ +type _Array = Array; + +/** + * An alias to the builtin `Record` type so we can + * easily alias it in import statements if there are name clashes. + */ +type _Record = Record; + +export type { + _Array as Array, + _BodyInit as BodyInit, + _HeadersInit as HeadersInit, + _Record as Record, + _RequestInfo as RequestInfo, + _RequestInit as RequestInit, + _Response as Response, +}; + +/** + * A copy of the builtin `EndingType` type as it isn't fully supported in certain + * environments and attempting to reference the global version will error. + * + * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L27941 + */ +type EndingType = 'native' | 'transparent'; + +/** + * A copy of the builtin `BlobPropertyBag` type as it isn't fully supported in certain + * environments and attempting to reference the global version will error. + * + * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L154 + * https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#options + */ +export interface BlobPropertyBag { + endings?: EndingType; + type?: string; +} + +/** + * A copy of the builtin `FilePropertyBag` type as it isn't fully supported in certain + * environments and attempting to reference the global version will error. + * + * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L503 + * https://developer.mozilla.org/en-US/docs/Web/API/File/File#options + */ +export interface FilePropertyBag extends BlobPropertyBag { + lastModified?: number; +} diff --git a/src/internal/detect-platform.ts b/src/internal/detect-platform.ts new file mode 100644 index 00000000..e82d95c9 --- /dev/null +++ b/src/internal/detect-platform.ts @@ -0,0 +1,196 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { VERSION } from '../version'; + +export const isRunningInBrowser = () => { + return ( + // @ts-ignore + typeof window !== 'undefined' && + // @ts-ignore + typeof window.document !== 'undefined' && + // @ts-ignore + typeof navigator !== 'undefined' + ); +}; + +type DetectedPlatform = 'deno' | 'node' | 'edge' | 'unknown'; + +/** + * Note this does not detect 'browser'; for that, use getBrowserInfo(). + */ +function getDetectedPlatform(): DetectedPlatform { + if (typeof Deno !== 'undefined' && Deno.build != null) { + return 'deno'; + } + if (typeof EdgeRuntime !== 'undefined') { + return 'edge'; + } + if ( + Object.prototype.toString.call( + typeof (globalThis as any).process !== 'undefined' ? (globalThis as any).process : 0, + ) === '[object process]' + ) { + return 'node'; + } + return 'unknown'; +} + +declare const Deno: any; +declare const EdgeRuntime: any; +type Arch = 'x32' | 'x64' | 'arm' | 'arm64' | `other:${string}` | 'unknown'; +type PlatformName = + | 'MacOS' + | 'Linux' + | 'Windows' + | 'FreeBSD' + | 'OpenBSD' + | 'iOS' + | 'Android' + | `Other:${string}` + | 'Unknown'; +type Browser = 'ie' | 'edge' | 'chrome' | 'firefox' | 'safari'; +type PlatformProperties = { + 'X-Stainless-Lang': 'js'; + 'X-Stainless-Package-Version': string; + 'X-Stainless-OS': PlatformName; + 'X-Stainless-Arch': Arch; + 'X-Stainless-Runtime': 'node' | 'deno' | 'edge' | `browser:${Browser}` | 'unknown'; + 'X-Stainless-Runtime-Version': string; +}; +const getPlatformProperties = (): PlatformProperties => { + const detectedPlatform = getDetectedPlatform(); + if (detectedPlatform === 'deno') { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': normalizePlatform(Deno.build.os), + 'X-Stainless-Arch': normalizeArch(Deno.build.arch), + 'X-Stainless-Runtime': 'deno', + 'X-Stainless-Runtime-Version': + typeof Deno.version === 'string' ? Deno.version : Deno.version?.deno ?? 'unknown', + }; + } + if (typeof EdgeRuntime !== 'undefined') { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': 'Unknown', + 'X-Stainless-Arch': `other:${EdgeRuntime}`, + 'X-Stainless-Runtime': 'edge', + 'X-Stainless-Runtime-Version': (globalThis as any).process.version, + }; + } + // Check if Node.js + if (detectedPlatform === 'node') { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': normalizePlatform((globalThis as any).process.platform ?? 'unknown'), + 'X-Stainless-Arch': normalizeArch((globalThis as any).process.arch ?? 'unknown'), + 'X-Stainless-Runtime': 'node', + 'X-Stainless-Runtime-Version': (globalThis as any).process.version ?? 'unknown', + }; + } + + const browserInfo = getBrowserInfo(); + if (browserInfo) { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': 'Unknown', + 'X-Stainless-Arch': 'unknown', + 'X-Stainless-Runtime': `browser:${browserInfo.browser}`, + 'X-Stainless-Runtime-Version': browserInfo.version, + }; + } + + // TODO add support for Cloudflare workers, etc. + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': 'Unknown', + 'X-Stainless-Arch': 'unknown', + 'X-Stainless-Runtime': 'unknown', + 'X-Stainless-Runtime-Version': 'unknown', + }; +}; + +type BrowserInfo = { + browser: Browser; + version: string; +}; + +declare const navigator: { userAgent: string } | undefined; + +// Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts +function getBrowserInfo(): BrowserInfo | null { + if (typeof navigator === 'undefined' || !navigator) { + return null; + } + + // NOTE: The order matters here! + const browserPatterns = [ + { key: 'edge' as const, pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'ie' as const, pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'ie' as const, pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'chrome' as const, pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'firefox' as const, pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'safari' as const, pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ }, + ]; + + // Find the FIRST matching browser + for (const { key, pattern } of browserPatterns) { + const match = pattern.exec(navigator.userAgent); + if (match) { + const major = match[1] || 0; + const minor = match[2] || 0; + const patch = match[3] || 0; + + return { browser: key, version: `${major}.${minor}.${patch}` }; + } + } + + return null; +} + +const normalizeArch = (arch: string): Arch => { + // Node docs: + // - https://nodejs.org/api/process.html#processarch + // Deno docs: + // - https://doc.deno.land/deno/stable/~/Deno.build + if (arch === 'x32') return 'x32'; + if (arch === 'x86_64' || arch === 'x64') return 'x64'; + if (arch === 'arm') return 'arm'; + if (arch === 'aarch64' || arch === 'arm64') return 'arm64'; + if (arch) return `other:${arch}`; + return 'unknown'; +}; + +const normalizePlatform = (platform: string): PlatformName => { + // Node platforms: + // - https://nodejs.org/api/process.html#processplatform + // Deno platforms: + // - https://doc.deno.land/deno/stable/~/Deno.build + // - https://github.com/denoland/deno/issues/14799 + + platform = platform.toLowerCase(); + + // NOTE: this iOS check is untested and may not work + // Node does not work natively on IOS, there is a fork at + // https://github.com/nodejs-mobile/nodejs-mobile + // however it is unknown at the time of writing how to detect if it is running + if (platform.includes('ios')) return 'iOS'; + if (platform === 'android') return 'Android'; + if (platform === 'darwin') return 'MacOS'; + if (platform === 'win32') return 'Windows'; + if (platform === 'freebsd') return 'FreeBSD'; + if (platform === 'openbsd') return 'OpenBSD'; + if (platform === 'linux') return 'Linux'; + if (platform) return `Other:${platform}`; + return 'Unknown'; +}; + +let _platformHeaders: PlatformProperties; +export const getPlatformHeaders = () => { + return (_platformHeaders ??= getPlatformProperties()); +}; diff --git a/src/internal/errors.ts b/src/internal/errors.ts new file mode 100644 index 00000000..82c7b14d --- /dev/null +++ b/src/internal/errors.ts @@ -0,0 +1,33 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export function isAbortError(err: unknown) { + return ( + typeof err === 'object' && + err !== null && + // Spec-compliant fetch implementations + (('name' in err && (err as any).name === 'AbortError') || + // Expo fetch + ('message' in err && String((err as any).message).includes('FetchRequestCanceledException'))) + ); +} + +export const castToError = (err: any): Error => { + if (err instanceof Error) return err; + if (typeof err === 'object' && err !== null) { + try { + if (Object.prototype.toString.call(err) === '[object Error]') { + // @ts-ignore - not all envs have native support for cause yet + const error = new Error(err.message, err.cause ? { cause: err.cause } : {}); + if (err.stack) error.stack = err.stack; + // @ts-ignore - not all envs have native support for cause yet + if (err.cause && !error.cause) error.cause = err.cause; + if (err.name) error.name = err.name; + return error; + } + } catch {} + try { + return new Error(JSON.stringify(err)); + } catch {} + } + return new Error(err); +}; diff --git a/src/internal/headers.ts b/src/internal/headers.ts new file mode 100644 index 00000000..c724a9d2 --- /dev/null +++ b/src/internal/headers.ts @@ -0,0 +1,97 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isReadonlyArray } from './utils/values'; + +type HeaderValue = string | undefined | null; +export type HeadersLike = + | Headers + | readonly HeaderValue[][] + | Record + | undefined + | null + | NullableHeaders; + +const brand_privateNullableHeaders = /* @__PURE__ */ Symbol('brand.privateNullableHeaders'); + +/** + * @internal + * Users can pass explicit nulls to unset default headers. When we parse them + * into a standard headers type we need to preserve that information. + */ +export type NullableHeaders = { + /** Brand check, prevent users from creating a NullableHeaders. */ + [brand_privateNullableHeaders]: true; + /** Parsed headers. */ + values: Headers; + /** Set of lowercase header names explicitly set to null. */ + nulls: Set; +}; + +function* iterateHeaders(headers: HeadersLike): IterableIterator { + if (!headers) return; + + if (brand_privateNullableHeaders in headers) { + const { values, nulls } = headers; + yield* values.entries(); + for (const name of nulls) { + yield [name, null]; + } + return; + } + + let shouldClear = false; + let iter: Iterable; + if (headers instanceof Headers) { + iter = headers.entries(); + } else if (isReadonlyArray(headers)) { + iter = headers; + } else { + shouldClear = true; + iter = Object.entries(headers ?? {}); + } + for (let row of iter) { + const name = row[0]; + if (typeof name !== 'string') throw new TypeError('expected header name to be a string'); + const values = isReadonlyArray(row[1]) ? row[1] : [row[1]]; + let didClear = false; + for (const value of values) { + if (value === undefined) continue; + + // Objects keys always overwrite older headers, they never append. + // Yield a null to clear the header before adding the new values. + if (shouldClear && !didClear) { + didClear = true; + yield [name, null]; + } + yield [name, value]; + } + } +} + +export const buildHeaders = (newHeaders: HeadersLike[]): NullableHeaders => { + const targetHeaders = new Headers(); + const nullHeaders = new Set(); + for (const headers of newHeaders) { + const seenHeaders = new Set(); + for (const [name, value] of iterateHeaders(headers)) { + const lowerName = name.toLowerCase(); + if (!seenHeaders.has(lowerName)) { + targetHeaders.delete(name); + seenHeaders.add(lowerName); + } + if (value === null) { + targetHeaders.delete(name); + nullHeaders.add(lowerName); + } else { + targetHeaders.append(name, value); + nullHeaders.delete(lowerName); + } + } + } + return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders }; +}; + +export const isEmptyHeaders = (headers: HeadersLike) => { + for (const _ of iterateHeaders(headers)) return false; + return true; +}; diff --git a/src/internal/parse.ts b/src/internal/parse.ts new file mode 100644 index 00000000..a40e7311 --- /dev/null +++ b/src/internal/parse.ts @@ -0,0 +1,50 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import type { FinalRequestOptions } from './request-options'; +import { type Terminal49 } from '../client'; +import { formatRequestDetails, loggerFor } from './utils/log'; + +export type APIResponseProps = { + response: Response; + options: FinalRequestOptions; + controller: AbortController; + requestLogID: string; + retryOfRequestLogID: string | undefined; + startTime: number; +}; + +export async function defaultParseResponse(client: Terminal49, props: APIResponseProps): Promise { + const { response, requestLogID, retryOfRequestLogID, startTime } = props; + const body = await (async () => { + // fetch refuses to read the body when the status code is 204. + if (response.status === 204) { + return null as T; + } + + if (props.options.__binaryResponse) { + return response as unknown as T; + } + + const contentType = response.headers.get('content-type'); + const mediaType = contentType?.split(';')[0]?.trim(); + const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json'); + if (isJSON) { + const json = await response.json(); + return json as T; + } + + const text = await response.text(); + return text as unknown as T; + })(); + loggerFor(client).debug( + `[${requestLogID}] response parsed`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + body, + durationMs: Date.now() - startTime, + }), + ); + return body; +} diff --git a/src/internal/request-options.ts b/src/internal/request-options.ts new file mode 100644 index 00000000..2aabf9aa --- /dev/null +++ b/src/internal/request-options.ts @@ -0,0 +1,91 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { NullableHeaders } from './headers'; + +import type { BodyInit } from './builtin-types'; +import type { HTTPMethod, MergedRequestInit } from './types'; +import { type HeadersLike } from './headers'; + +export type FinalRequestOptions = RequestOptions & { method: HTTPMethod; path: string }; + +export type RequestOptions = { + /** + * The HTTP method for the request (e.g., 'get', 'post', 'put', 'delete'). + */ + method?: HTTPMethod; + + /** + * The URL path for the request. + * + * @example "/v1/foo" + */ + path?: string; + + /** + * Query parameters to include in the request URL. + */ + query?: object | undefined | null; + + /** + * The request body. Can be a string, JSON object, FormData, or other supported types. + */ + body?: unknown; + + /** + * HTTP headers to include with the request. Can be a Headers object, plain object, or array of tuples. + */ + headers?: HeadersLike; + + /** + * The maximum number of times that the client will retry a request in case of a + * temporary failure, like a network error or a 5XX error from the server. + * + * @default 2 + */ + maxRetries?: number; + + stream?: boolean | undefined; + + /** + * The maximum amount of time (in milliseconds) that the client should wait for a response + * from the server before timing out a single request. + * + * @unit milliseconds + */ + timeout?: number; + + /** + * Additional `RequestInit` options to be passed to the underlying `fetch` call. + * These options will be merged with the client's default fetch options. + */ + fetchOptions?: MergedRequestInit; + + /** + * An AbortSignal that can be used to cancel the request. + */ + signal?: AbortSignal | undefined | null; + + /** + * A unique key for this request to enable idempotency. + */ + idempotencyKey?: string; + + /** + * Override the default base URL for this specific request. + */ + defaultBaseURL?: string | undefined; + + __binaryResponse?: boolean | undefined; +}; + +export type EncodedContent = { bodyHeaders: HeadersLike; body: BodyInit }; +export type RequestEncoder = (request: { headers: NullableHeaders; body: unknown }) => EncodedContent; + +export const FallbackEncoder: RequestEncoder = ({ headers, body }) => { + return { + bodyHeaders: { + 'content-type': 'application/json', + }, + body: JSON.stringify(body), + }; +}; diff --git a/src/internal/shim-types.ts b/src/internal/shim-types.ts new file mode 100644 index 00000000..8ddf7b0a --- /dev/null +++ b/src/internal/shim-types.ts @@ -0,0 +1,26 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +/** + * Shims for types that we can't always rely on being available globally. + * + * Note: these only exist at the type-level, there is no corresponding runtime + * version for any of these symbols. + */ + +type NeverToAny = T extends never ? any : T; + +/** @ts-ignore */ +type _DOMReadableStream = globalThis.ReadableStream; + +/** @ts-ignore */ +type _NodeReadableStream = import('stream/web').ReadableStream; + +type _ConditionalNodeReadableStream = + typeof globalThis extends { ReadableStream: any } ? never : _NodeReadableStream; + +type _ReadableStream = NeverToAny< + | ([0] extends [1 & _DOMReadableStream] ? never : _DOMReadableStream) + | ([0] extends [1 & _ConditionalNodeReadableStream] ? never : _ConditionalNodeReadableStream) +>; + +export type { _ReadableStream as ReadableStream }; diff --git a/src/internal/shims.ts b/src/internal/shims.ts new file mode 100644 index 00000000..e597ccab --- /dev/null +++ b/src/internal/shims.ts @@ -0,0 +1,107 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +/** + * This module provides internal shims and utility functions for environments where certain Node.js or global types may not be available. + * + * These are used to ensure we can provide a consistent behaviour between different JavaScript environments and good error + * messages in cases where an environment isn't fully supported. + */ + +import type { Fetch } from './builtin-types'; +import type { ReadableStream } from './shim-types'; + +export function getDefaultFetch(): Fetch { + if (typeof fetch !== 'undefined') { + return fetch as any; + } + + throw new Error( + '`fetch` is not defined as a global; Either pass `fetch` to the client, `new Terminal49({ fetch })` or polyfill the global, `globalThis.fetch = fetch`', + ); +} + +type ReadableStreamArgs = ConstructorParameters; + +export function makeReadableStream(...args: ReadableStreamArgs): ReadableStream { + const ReadableStream = (globalThis as any).ReadableStream; + if (typeof ReadableStream === 'undefined') { + // Note: All of the platforms / runtimes we officially support already define + // `ReadableStream` as a global, so this should only ever be hit on unsupported runtimes. + throw new Error( + '`ReadableStream` is not defined as a global; You will need to polyfill it, `globalThis.ReadableStream = ReadableStream`', + ); + } + + return new ReadableStream(...args); +} + +export function ReadableStreamFrom(iterable: Iterable | AsyncIterable): ReadableStream { + let iter: AsyncIterator | Iterator = + Symbol.asyncIterator in iterable ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator](); + + return makeReadableStream({ + start() {}, + async pull(controller: any) { + const { done, value } = await iter.next(); + if (done) { + controller.close(); + } else { + controller.enqueue(value); + } + }, + async cancel() { + await iter.return?.(); + }, + }); +} + +/** + * Most browsers don't yet have async iterable support for ReadableStream, + * and Node has a very different way of reading bytes from its "ReadableStream". + * + * This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490 + */ +export function ReadableStreamToAsyncIterable(stream: any): AsyncIterableIterator { + if (stream[Symbol.asyncIterator]) return stream; + + const reader = stream.getReader(); + return { + async next() { + try { + const result = await reader.read(); + if (result?.done) reader.releaseLock(); // release lock when stream becomes closed + return result; + } catch (e) { + reader.releaseLock(); // release lock when stream becomes errored + throw e; + } + }, + async return() { + const cancelPromise = reader.cancel(); + reader.releaseLock(); + await cancelPromise; + return { done: true, value: undefined }; + }, + [Symbol.asyncIterator]() { + return this; + }, + }; +} + +/** + * Cancels a ReadableStream we don't need to consume. + * See https://undici.nodejs.org/#/?id=garbage-collection + */ +export async function CancelReadableStream(stream: any): Promise { + if (stream === null || typeof stream !== 'object') return; + + if (stream[Symbol.asyncIterator]) { + await stream[Symbol.asyncIterator]().return?.(); + return; + } + + const reader = stream.getReader(); + const cancelPromise = reader.cancel(); + reader.releaseLock(); + await cancelPromise; +} diff --git a/src/internal/to-file.ts b/src/internal/to-file.ts new file mode 100644 index 00000000..30eada32 --- /dev/null +++ b/src/internal/to-file.ts @@ -0,0 +1,154 @@ +import { BlobPart, getName, makeFile, isAsyncIterable } from './uploads'; +import type { FilePropertyBag } from './builtin-types'; +import { checkFileSupport } from './uploads'; + +type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | DataView; + +/** + * Intended to match DOM Blob, node-fetch Blob, node:buffer Blob, etc. + * Don't add arrayBuffer here, node-fetch doesn't have it + */ +interface BlobLike { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */ + readonly size: number; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */ + readonly type: string; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */ + text(): Promise; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */ + slice(start?: number, end?: number): BlobLike; +} + +/** + * This check adds the arrayBuffer() method type because it is available and used at runtime + */ +const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise } => + value != null && + typeof value === 'object' && + typeof value.size === 'number' && + typeof value.type === 'string' && + typeof value.text === 'function' && + typeof value.slice === 'function' && + typeof value.arrayBuffer === 'function'; + +/** + * Intended to match DOM File, node:buffer File, undici File, etc. + */ +interface FileLike extends BlobLike { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */ + readonly lastModified: number; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */ + readonly name?: string | undefined; +} + +/** + * This check adds the arrayBuffer() method type because it is available and used at runtime + */ +const isFileLike = (value: any): value is FileLike & { arrayBuffer(): Promise } => + value != null && + typeof value === 'object' && + typeof value.name === 'string' && + typeof value.lastModified === 'number' && + isBlobLike(value); + +/** + * Intended to match DOM Response, node-fetch Response, undici Response, etc. + */ +export interface ResponseLike { + url: string; + blob(): Promise; +} + +const isResponseLike = (value: any): value is ResponseLike => + value != null && + typeof value === 'object' && + typeof value.url === 'string' && + typeof value.blob === 'function'; + +export type ToFileInput = + | FileLike + | ResponseLike + | Exclude + | AsyncIterable; + +/** + * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats + * @param value the raw content of the file. Can be an {@link Uploadable}, BlobLikePart, or AsyncIterable of BlobLikeParts + * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible + * @param {Object=} options additional properties + * @param {string=} options.type the MIME type of the content + * @param {number=} options.lastModified the last modified timestamp + * @returns a {@link File} with the given properties + */ +export async function toFile( + value: ToFileInput | PromiseLike, + name?: string | null | undefined, + options?: FilePropertyBag | undefined, +): Promise { + checkFileSupport(); + + // If it's a promise, resolve it. + value = await value; + + // If we've been given a `File` we don't need to do anything + if (isFileLike(value)) { + if (value instanceof File) { + return value; + } + return makeFile([await value.arrayBuffer()], value.name); + } + + if (isResponseLike(value)) { + const blob = await value.blob(); + name ||= new URL(value.url).pathname.split(/[\\/]/).pop(); + + return makeFile(await getBytes(blob), name, options); + } + + const parts = await getBytes(value); + + name ||= getName(value); + + if (!options?.type) { + const type = parts.find((part) => typeof part === 'object' && 'type' in part && part.type); + if (typeof type === 'string') { + options = { ...options, type }; + } + } + + return makeFile(parts, name, options); +} + +async function getBytes(value: BlobLikePart | AsyncIterable): Promise> { + let parts: Array = []; + if ( + typeof value === 'string' || + ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc. + value instanceof ArrayBuffer + ) { + parts.push(value); + } else if (isBlobLike(value)) { + parts.push(value instanceof Blob ? value : await value.arrayBuffer()); + } else if ( + isAsyncIterable(value) // includes Readable, ReadableStream, etc. + ) { + for await (const chunk of value) { + parts.push(...(await getBytes(chunk as BlobLikePart))); // TODO, consider validating? + } + } else { + const constructor = value?.constructor?.name; + throw new Error( + `Unexpected data type: ${typeof value}${ + constructor ? `; constructor: ${constructor}` : '' + }${propsForError(value)}`, + ); + } + + return parts; +} + +function propsForError(value: unknown): string { + if (typeof value !== 'object' || value === null) return ''; + const props = Object.getOwnPropertyNames(value); + return `; props: [${props.map((p) => `"${p}"`).join(', ')}]`; +} diff --git a/src/internal/types.ts b/src/internal/types.ts new file mode 100644 index 00000000..b668dfc0 --- /dev/null +++ b/src/internal/types.ts @@ -0,0 +1,95 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export type PromiseOrValue = T | Promise; +export type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'; + +export type KeysEnum = { [P in keyof Required]: true }; + +export type FinalizedRequestInit = RequestInit & { headers: Headers }; + +type NotAny = [0] extends [1 & T] ? never : T; + +/** + * Some environments overload the global fetch function, and Parameters only gets the last signature. + */ +type OverloadedParameters = + T extends ( + { + (...args: infer A): unknown; + (...args: infer B): unknown; + (...args: infer C): unknown; + (...args: infer D): unknown; + } + ) ? + A | B | C | D + : T extends ( + { + (...args: infer A): unknown; + (...args: infer B): unknown; + (...args: infer C): unknown; + } + ) ? + A | B | C + : T extends ( + { + (...args: infer A): unknown; + (...args: infer B): unknown; + } + ) ? + A | B + : T extends (...args: infer A) => unknown ? A + : never; + +/* eslint-disable */ +/** + * These imports attempt to get types from a parent package's dependencies. + * Unresolved bare specifiers can trigger [automatic type acquisition][1] in some projects, which + * would cause typescript to show types not present at runtime. To avoid this, we import + * directly from parent node_modules folders. + * + * We need to check multiple levels because we don't know what directory structure we'll be in. + * For example, pnpm generates directories like this: + * ``` + * node_modules + * ├── .pnpm + * │ └── pkg@1.0.0 + * │ └── node_modules + * │ └── pkg + * │ └── internal + * │ └── types.d.ts + * ├── pkg -> .pnpm/pkg@1.0.0/node_modules/pkg + * └── undici + * ``` + * + * [1]: https://www.typescriptlang.org/tsconfig/#typeAcquisition + */ +/** @ts-ignore For users with \@types/node */ +type UndiciTypesRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users with undici */ +type UndiciRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users with \@types/bun */ +type BunRequestInit = globalThis.FetchRequestInit; +/** @ts-ignore For users with node-fetch@2 */ +type NodeFetch2RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users with node-fetch@3, doesn't need file extension because types are at ./@types/index.d.ts */ +type NodeFetch3RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users who use Deno */ +type FetchRequestInit = NonNullable[1]>; +/* eslint-enable */ + +type RequestInits = + | NotAny + | NotAny + | NotAny + | NotAny + | NotAny + | NotAny + | NotAny; + +/** + * This type contains `RequestInit` options that may be available on the current runtime, + * including per-platform extensions like `dispatcher`, `agent`, `client`, etc. + */ +export type MergedRequestInit = RequestInits & + /** We don't include these in the types as they'll be overridden for every request. */ + Partial>; diff --git a/src/internal/uploads.ts b/src/internal/uploads.ts new file mode 100644 index 00000000..4bd65c7b --- /dev/null +++ b/src/internal/uploads.ts @@ -0,0 +1,187 @@ +import { type RequestOptions } from './request-options'; +import type { FilePropertyBag, Fetch } from './builtin-types'; +import type { Terminal49 } from '../client'; +import { ReadableStreamFrom } from './shims'; + +export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | DataView; +type FsReadStream = AsyncIterable & { path: string | { toString(): string } }; + +// https://github.com/oven-sh/bun/issues/5980 +interface BunFile extends Blob { + readonly name?: string | undefined; +} + +export const checkFileSupport = () => { + if (typeof File === 'undefined') { + const { process } = globalThis as any; + const isOldNode = + typeof process?.versions?.node === 'string' && parseInt(process.versions.node.split('.')) < 20; + throw new Error( + '`File` is not defined as a global, which is required for file uploads.' + + (isOldNode ? + " Update to Node 20 LTS or newer, or set `globalThis.File` to `import('node:buffer').File`." + : ''), + ); + } +}; + +/** + * Typically, this is a native "File" class. + * + * We provide the {@link toFile} utility to convert a variety of objects + * into the File class. + * + * For convenience, you can also pass a fetch Response, or in Node, + * the result of fs.createReadStream(). + */ +export type Uploadable = File | Response | FsReadStream | BunFile; + +/** + * Construct a `File` instance. This is used to ensure a helpful error is thrown + * for environments that don't define a global `File` yet. + */ +export function makeFile( + fileBits: BlobPart[], + fileName: string | undefined, + options?: FilePropertyBag, +): File { + checkFileSupport(); + return new File(fileBits as any, fileName ?? 'unknown_file', options); +} + +export function getName(value: any): string | undefined { + return ( + ( + (typeof value === 'object' && + value !== null && + (('name' in value && value.name && String(value.name)) || + ('url' in value && value.url && String(value.url)) || + ('filename' in value && value.filename && String(value.filename)) || + ('path' in value && value.path && String(value.path)))) || + '' + ) + .split(/[\\/]/) + .pop() || undefined + ); +} + +export const isAsyncIterable = (value: any): value is AsyncIterable => + value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function'; + +/** + * Returns a multipart/form-data request if any part of the given request body contains a File / Blob value. + * Otherwise returns the request as is. + */ +export const maybeMultipartFormRequestOptions = async ( + opts: RequestOptions, + fetch: Terminal49 | Fetch, +): Promise => { + if (!hasUploadableValue(opts.body)) return opts; + + return { ...opts, body: await createForm(opts.body, fetch) }; +}; + +type MultipartFormRequestOptions = Omit & { body: unknown }; + +export const multipartFormRequestOptions = async ( + opts: MultipartFormRequestOptions, + fetch: Terminal49 | Fetch, +): Promise => { + return { ...opts, body: await createForm(opts.body, fetch) }; +}; + +const supportsFormDataMap = /* @__PURE__ */ new WeakMap>(); + +/** + * node-fetch doesn't support the global FormData object in recent node versions. Instead of sending + * properly-encoded form data, it just stringifies the object, resulting in a request body of "[object FormData]". + * This function detects if the fetch function provided supports the global FormData object to avoid + * confusing error messages later on. + */ +function supportsFormData(fetchObject: Terminal49 | Fetch): Promise { + const fetch: Fetch = typeof fetchObject === 'function' ? fetchObject : (fetchObject as any).fetch; + const cached = supportsFormDataMap.get(fetch); + if (cached) return cached; + const promise = (async () => { + try { + const FetchResponse = ( + 'Response' in fetch ? + fetch.Response + : (await fetch('data:,')).constructor) as typeof Response; + const data = new FormData(); + if (data.toString() === (await new FetchResponse(data).text())) { + return false; + } + return true; + } catch { + // avoid false negatives + return true; + } + })(); + supportsFormDataMap.set(fetch, promise); + return promise; +} + +export const createForm = async >( + body: T | undefined, + fetch: Terminal49 | Fetch, +): Promise => { + if (!(await supportsFormData(fetch))) { + throw new TypeError( + 'The provided fetch function does not support file uploads with the current global FormData class.', + ); + } + const form = new FormData(); + await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value))); + return form; +}; + +// We check for Blob not File because Bun.File doesn't inherit from File, +// but they both inherit from Blob and have a `name` property at runtime. +const isNamedBlob = (value: unknown) => value instanceof Blob && 'name' in value; + +const isUploadable = (value: unknown) => + typeof value === 'object' && + value !== null && + (value instanceof Response || isAsyncIterable(value) || isNamedBlob(value)); + +const hasUploadableValue = (value: unknown): boolean => { + if (isUploadable(value)) return true; + if (Array.isArray(value)) return value.some(hasUploadableValue); + if (value && typeof value === 'object') { + for (const k in value) { + if (hasUploadableValue((value as any)[k])) return true; + } + } + return false; +}; + +const addFormValue = async (form: FormData, key: string, value: unknown): Promise => { + if (value === undefined) return; + if (value == null) { + throw new TypeError( + `Received null for "${key}"; to pass null in FormData, you must use the string 'null'`, + ); + } + + // TODO: make nested formats configurable + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + form.append(key, String(value)); + } else if (value instanceof Response) { + form.append(key, makeFile([await value.blob()], getName(value))); + } else if (isAsyncIterable(value)) { + form.append(key, makeFile([await new Response(ReadableStreamFrom(value)).blob()], getName(value))); + } else if (isNamedBlob(value)) { + form.append(key, value, getName(value)); + } else if (Array.isArray(value)) { + await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry))); + } else if (typeof value === 'object') { + await Promise.all( + Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)), + ); + } else { + throw new TypeError( + `Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`, + ); + } +}; diff --git a/src/internal/utils.ts b/src/internal/utils.ts new file mode 100644 index 00000000..3cbfacce --- /dev/null +++ b/src/internal/utils.ts @@ -0,0 +1,8 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export * from './utils/values'; +export * from './utils/base64'; +export * from './utils/env'; +export * from './utils/log'; +export * from './utils/uuid'; +export * from './utils/sleep'; diff --git a/src/internal/utils/base64.ts b/src/internal/utils/base64.ts new file mode 100644 index 00000000..2ffd9ef1 --- /dev/null +++ b/src/internal/utils/base64.ts @@ -0,0 +1,40 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Terminal49Error } from '../../core/error'; +import { encodeUTF8 } from './bytes'; + +export const toBase64 = (data: string | Uint8Array | null | undefined): string => { + if (!data) return ''; + + if (typeof (globalThis as any).Buffer !== 'undefined') { + return (globalThis as any).Buffer.from(data).toString('base64'); + } + + if (typeof data === 'string') { + data = encodeUTF8(data); + } + + if (typeof btoa !== 'undefined') { + return btoa(String.fromCharCode.apply(null, data as any)); + } + + throw new Terminal49Error('Cannot generate base64 string; Expected `Buffer` or `btoa` to be defined'); +}; + +export const fromBase64 = (str: string): Uint8Array => { + if (typeof (globalThis as any).Buffer !== 'undefined') { + const buf = (globalThis as any).Buffer.from(str, 'base64'); + return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); + } + + if (typeof atob !== 'undefined') { + const bstr = atob(str); + const buf = new Uint8Array(bstr.length); + for (let i = 0; i < bstr.length; i++) { + buf[i] = bstr.charCodeAt(i); + } + return buf; + } + + throw new Terminal49Error('Cannot decode base64 string; Expected `Buffer` or `atob` to be defined'); +}; diff --git a/src/internal/utils/bytes.ts b/src/internal/utils/bytes.ts new file mode 100644 index 00000000..8da627ab --- /dev/null +++ b/src/internal/utils/bytes.ts @@ -0,0 +1,32 @@ +export function concatBytes(buffers: Uint8Array[]): Uint8Array { + let length = 0; + for (const buffer of buffers) { + length += buffer.length; + } + const output = new Uint8Array(length); + let index = 0; + for (const buffer of buffers) { + output.set(buffer, index); + index += buffer.length; + } + + return output; +} + +let encodeUTF8_: (str: string) => Uint8Array; +export function encodeUTF8(str: string) { + let encoder; + return ( + encodeUTF8_ ?? + ((encoder = new (globalThis as any).TextEncoder()), (encodeUTF8_ = encoder.encode.bind(encoder))) + )(str); +} + +let decodeUTF8_: (bytes: Uint8Array) => string; +export function decodeUTF8(bytes: Uint8Array) { + let decoder; + return ( + decodeUTF8_ ?? + ((decoder = new (globalThis as any).TextDecoder()), (decodeUTF8_ = decoder.decode.bind(decoder))) + )(bytes); +} diff --git a/src/internal/utils/env.ts b/src/internal/utils/env.ts new file mode 100644 index 00000000..2d848007 --- /dev/null +++ b/src/internal/utils/env.ts @@ -0,0 +1,18 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +/** + * Read an environment variable. + * + * Trims beginning and trailing whitespace. + * + * Will return undefined if the environment variable doesn't exist or cannot be accessed. + */ +export const readEnv = (env: string): string | undefined => { + if (typeof (globalThis as any).process !== 'undefined') { + return (globalThis as any).process.env?.[env]?.trim() ?? undefined; + } + if (typeof (globalThis as any).Deno !== 'undefined') { + return (globalThis as any).Deno.env?.get?.(env)?.trim(); + } + return undefined; +}; diff --git a/src/internal/utils/log.ts b/src/internal/utils/log.ts new file mode 100644 index 00000000..ef41cdcd --- /dev/null +++ b/src/internal/utils/log.ts @@ -0,0 +1,126 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { hasOwn } from './values'; +import { type Terminal49 } from '../../client'; +import { RequestOptions } from '../request-options'; + +type LogFn = (message: string, ...rest: unknown[]) => void; +export type Logger = { + error: LogFn; + warn: LogFn; + info: LogFn; + debug: LogFn; +}; +export type LogLevel = 'off' | 'error' | 'warn' | 'info' | 'debug'; + +const levelNumbers = { + off: 0, + error: 200, + warn: 300, + info: 400, + debug: 500, +}; + +export const parseLogLevel = ( + maybeLevel: string | undefined, + sourceName: string, + client: Terminal49, +): LogLevel | undefined => { + if (!maybeLevel) { + return undefined; + } + if (hasOwn(levelNumbers, maybeLevel)) { + return maybeLevel; + } + loggerFor(client).warn( + `${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify( + Object.keys(levelNumbers), + )}`, + ); + return undefined; +}; + +function noop() {} + +function makeLogFn(fnLevel: keyof Logger, logger: Logger | undefined, logLevel: LogLevel) { + if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) { + return noop; + } else { + // Don't wrap logger functions, we want the stacktrace intact! + return logger[fnLevel].bind(logger); + } +} + +const noopLogger = { + error: noop, + warn: noop, + info: noop, + debug: noop, +}; + +let cachedLoggers = /* @__PURE__ */ new WeakMap(); + +export function loggerFor(client: Terminal49): Logger { + const logger = client.logger; + const logLevel = client.logLevel ?? 'off'; + if (!logger) { + return noopLogger; + } + + const cachedLogger = cachedLoggers.get(logger); + if (cachedLogger && cachedLogger[0] === logLevel) { + return cachedLogger[1]; + } + + const levelLogger = { + error: makeLogFn('error', logger, logLevel), + warn: makeLogFn('warn', logger, logLevel), + info: makeLogFn('info', logger, logLevel), + debug: makeLogFn('debug', logger, logLevel), + }; + + cachedLoggers.set(logger, [logLevel, levelLogger]); + + return levelLogger; +} + +export const formatRequestDetails = (details: { + options?: RequestOptions | undefined; + headers?: Headers | Record | undefined; + retryOfRequestLogID?: string | undefined; + retryOf?: string | undefined; + url?: string | undefined; + status?: number | undefined; + method?: string | undefined; + durationMs?: number | undefined; + message?: unknown; + body?: unknown; +}) => { + if (details.options) { + details.options = { ...details.options }; + delete details.options['headers']; // redundant + leaks internals + } + if (details.headers) { + details.headers = Object.fromEntries( + (details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map( + ([name, value]) => [ + name, + ( + name.toLowerCase() === 'authorization' || + name.toLowerCase() === 'cookie' || + name.toLowerCase() === 'set-cookie' + ) ? + '***' + : value, + ], + ), + ); + } + if ('retryOfRequestLogID' in details) { + if (details.retryOfRequestLogID) { + details.retryOf = details.retryOfRequestLogID; + } + delete details.retryOfRequestLogID; + } + return details; +}; diff --git a/src/internal/utils/path.ts b/src/internal/utils/path.ts new file mode 100644 index 00000000..3c965770 --- /dev/null +++ b/src/internal/utils/path.ts @@ -0,0 +1,88 @@ +import { Terminal49Error } from '../../core/error'; + +/** + * Percent-encode everything that isn't safe to have in a path without encoding safe chars. + * + * Taken from https://datatracker.ietf.org/doc/html/rfc3986#section-3.3: + * > unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * > sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" + * > pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + */ +export function encodeURIPath(str: string) { + return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent); +} + +const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null)); + +export const createPathTagFunction = (pathEncoder = encodeURIPath) => + function path(statics: readonly string[], ...params: readonly unknown[]): string { + // If there are no params, no processing is needed. + if (statics.length === 1) return statics[0]!; + + let postPath = false; + const invalidSegments = []; + const path = statics.reduce((previousValue, currentValue, index) => { + if (/[?#]/.test(currentValue)) { + postPath = true; + } + const value = params[index]; + let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value); + if ( + index !== params.length && + (value == null || + (typeof value === 'object' && + // handle values from other realms + value.toString === + Object.getPrototypeOf(Object.getPrototypeOf((value as any).hasOwnProperty ?? EMPTY) ?? EMPTY) + ?.toString)) + ) { + encoded = value + ''; + invalidSegments.push({ + start: previousValue.length + currentValue.length, + length: encoded.length, + error: `Value of type ${Object.prototype.toString + .call(value) + .slice(8, -1)} is not a valid path parameter`, + }); + } + return previousValue + currentValue + (index === params.length ? '' : encoded); + }, ''); + + const pathOnly = path.split(/[?#]/, 1)[0]!; + const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi; + let match; + + // Find all invalid segments + while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) { + invalidSegments.push({ + start: match.index, + length: match[0].length, + error: `Value "${match[0]}" can\'t be safely passed as a path parameter`, + }); + } + + invalidSegments.sort((a, b) => a.start - b.start); + + if (invalidSegments.length > 0) { + let lastEnd = 0; + const underline = invalidSegments.reduce((acc, segment) => { + const spaces = ' '.repeat(segment.start - lastEnd); + const arrows = '^'.repeat(segment.length); + lastEnd = segment.start + segment.length; + return acc + spaces + arrows; + }, ''); + + throw new Terminal49Error( + `Path parameters result in path with invalid segments:\n${invalidSegments + .map((e) => e.error) + .join('\n')}\n${path}\n${underline}`, + ); + } + + return path; + }; + +/** + * URI-encodes path params and ensures no unsafe /./ or /../ path segments are introduced. + */ +export const path = /* @__PURE__ */ createPathTagFunction(encodeURIPath); diff --git a/src/internal/utils/sleep.ts b/src/internal/utils/sleep.ts new file mode 100644 index 00000000..65e52962 --- /dev/null +++ b/src/internal/utils/sleep.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/internal/utils/uuid.ts b/src/internal/utils/uuid.ts new file mode 100644 index 00000000..b0e53aaf --- /dev/null +++ b/src/internal/utils/uuid.ts @@ -0,0 +1,17 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +/** + * https://stackoverflow.com/a/2117523 + */ +export let uuid4 = function () { + const { crypto } = globalThis as any; + if (crypto?.randomUUID) { + uuid4 = crypto.randomUUID.bind(crypto); + return crypto.randomUUID(); + } + const u8 = new Uint8Array(1); + const randomByte = crypto ? () => crypto.getRandomValues(u8)[0]! : () => (Math.random() * 0xff) & 0xff; + return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) => + (+c ^ (randomByte() & (15 >> (+c / 4)))).toString(16), + ); +}; diff --git a/src/internal/utils/values.ts b/src/internal/utils/values.ts new file mode 100644 index 00000000..b62083ed --- /dev/null +++ b/src/internal/utils/values.ts @@ -0,0 +1,105 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Terminal49Error } from '../../core/error'; + +// https://url.spec.whatwg.org/#url-scheme-string +const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i; + +export const isAbsoluteURL = (url: string): boolean => { + return startsWithSchemeRegexp.test(url); +}; + +export let isArray = (val: unknown): val is unknown[] => ((isArray = Array.isArray), isArray(val)); +export let isReadonlyArray = isArray as (val: unknown) => val is readonly unknown[]; + +/** Returns an object if the given value isn't an object, otherwise returns as-is */ +export function maybeObj(x: unknown): object { + if (typeof x !== 'object') { + return {}; + } + + return x ?? {}; +} + +// https://stackoverflow.com/a/34491287 +export function isEmptyObj(obj: Object | null | undefined): boolean { + if (!obj) return true; + for (const _k in obj) return false; + return true; +} + +// https://eslint.org/docs/latest/rules/no-prototype-builtins +export function hasOwn(obj: T, key: PropertyKey): key is keyof T { + return Object.prototype.hasOwnProperty.call(obj, key); +} + +export function isObj(obj: unknown): obj is Record { + return obj != null && typeof obj === 'object' && !Array.isArray(obj); +} + +export const ensurePresent = (value: T | null | undefined): T => { + if (value == null) { + throw new Terminal49Error(`Expected a value to be given but received ${value} instead.`); + } + + return value; +}; + +export const validatePositiveInteger = (name: string, n: unknown): number => { + if (typeof n !== 'number' || !Number.isInteger(n)) { + throw new Terminal49Error(`${name} must be an integer`); + } + if (n < 0) { + throw new Terminal49Error(`${name} must be a positive integer`); + } + return n; +}; + +export const coerceInteger = (value: unknown): number => { + if (typeof value === 'number') return Math.round(value); + if (typeof value === 'string') return parseInt(value, 10); + + throw new Terminal49Error(`Could not coerce ${value} (type: ${typeof value}) into a number`); +}; + +export const coerceFloat = (value: unknown): number => { + if (typeof value === 'number') return value; + if (typeof value === 'string') return parseFloat(value); + + throw new Terminal49Error(`Could not coerce ${value} (type: ${typeof value}) into a number`); +}; + +export const coerceBoolean = (value: unknown): boolean => { + if (typeof value === 'boolean') return value; + if (typeof value === 'string') return value === 'true'; + return Boolean(value); +}; + +export const maybeCoerceInteger = (value: unknown): number | undefined => { + if (value == null) { + return undefined; + } + return coerceInteger(value); +}; + +export const maybeCoerceFloat = (value: unknown): number | undefined => { + if (value == null) { + return undefined; + } + return coerceFloat(value); +}; + +export const maybeCoerceBoolean = (value: unknown): boolean | undefined => { + if (value == null) { + return undefined; + } + return coerceBoolean(value); +}; + +export const safeJSON = (text: string) => { + try { + return JSON.parse(text); + } catch (err) { + return undefined; + } +}; diff --git a/src/lib/.keep b/src/lib/.keep new file mode 100644 index 00000000..7554f8b2 --- /dev/null +++ b/src/lib/.keep @@ -0,0 +1,4 @@ +File generated from our OpenAPI spec by Stainless. + +This directory can be used to store custom files to expand the SDK. +It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. diff --git a/src/resource.ts b/src/resource.ts new file mode 100644 index 00000000..363e3516 --- /dev/null +++ b/src/resource.ts @@ -0,0 +1,2 @@ +/** @deprecated Import from ./core/resource instead */ +export * from './core/resource'; diff --git a/src/resources.ts b/src/resources.ts new file mode 100644 index 00000000..b283d578 --- /dev/null +++ b/src/resources.ts @@ -0,0 +1 @@ +export * from './resources/index'; diff --git a/src/resources/containers.ts b/src/resources/containers.ts new file mode 100644 index 00000000..072596b5 --- /dev/null +++ b/src/resources/containers.ts @@ -0,0 +1,809 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import * as MetroAreasAPI from './metro-areas'; +import * as PortsAPI from './ports'; +import * as ShipmentsAPI from './shipments'; +import * as TerminalsAPI from './terminals'; +import * as VesselsAPI from './vessels'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; +import { path } from '../internal/utils/path'; + +export class Containers extends APIResource { + /** + * Retrieves the details of a container. + */ + retrieve( + id: string, + query: ContainerRetrieveParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get(path`/containers/${id}`, { query, ...options }); + } + + /** + * Update a container + */ + update( + body: ContainerUpdateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.patch('/containers', { body, ...options }); + } + + /** + * Returns a list of container. The containers are returned sorted by creation + * date, with the most recently refreshed containers appearing first. + * + * This API will return all containers associated with the account. + */ + list( + query: ContainerListParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get('/containers', { query, ...options }); + } + + /** + * #### Deprecation warning + * + * The `raw_events` endpoint is provided as-is. + * + * For past events we recommend consuming `transport_events`. + * + * --- + * + * Get a list of past and future (estimated) milestones for a container as reported + * by the carrier. Some of the data is normalized even though the API is called + * raw_events. + * + * Normalized attributes: `event` and `timestamp` timestamp. Not all of the `event` + * values have been normalized. You can expect the the events related to container + * movements to be normalized but there are cases where events are not normalized. + * + * For past historical events we recommend consuming `transport_events`. Although + * there are fewer events here those events go through additional vetting and + * normalization to avoid false positives and get you correct data. + * + * @deprecated + */ + getRawEvents(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/containers/${id}/raw_events`, options); + } + + /** + * Get a list of past transport events (canonical) for a container. All data has + * been normalized across all carriers. These are a verified subset of the raw + * events may also be sent as Webhook Notifications to a webhook endpoint. + * + * This does not provide any estimated future events. See + * `container/:id/raw_events` endpoint for that. + */ + getTransportEvents( + id: string, + query: ContainerGetTransportEventsParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get(path`/containers/${id}/transport_events`, { query, ...options }); + } +} + +/** + * Represents the equipment during a specific journey. + */ +export interface Container { + id: string; + + attributes: Container.Attributes; + + type: 'container'; + + relationships?: Container.Relationships; +} + +export namespace Container { + export interface Attributes { + /** + * Whether Terminal 49 is receiving availability status from the terminal. + */ + availability_known?: boolean; + + /** + * If availability_known is true, then whether container is available to be picked + * up at terminal. + */ + available_for_pickup?: boolean | null; + + created_at?: string; + + /** + * Time empty container was returned. + */ + empty_terminated_at?: string | null; + + /** + * IANA tz. Applies to attribute empty_terminated_at. + */ + empty_terminated_timezone?: string | null; + + equipment_height?: 'standard' | 'high_cube' | null; + + equipment_length?: 10 | 20 | 40 | 45 | null; + + equipment_type?: 'dry' | 'reefer' | 'open top' | 'flat rack' | 'bulk' | 'tank' | null; + + fees_at_pod_terminal?: Array; + + /** + * Pickup time at final destination for inland moves. + */ + final_destination_full_out_at?: string | null; + + /** + * IANA tz. Applies to attribute final_destination_full_out_at. + */ + final_destination_timezone?: string | null; + + holds_at_pod_terminal?: Array; + + ind_ata_at?: string | null; + + ind_eta_at?: string | null; + + ind_facility_lfd_on?: string | null; + + /** + * The SCAC of the rail carrier for the delivery leg of the container's + * journey.(BETA) + */ + ind_rail_carrier_scac?: string | null; + + ind_rail_unloaded_at?: string | null; + + /** + * Location at port of discharge terminal + */ + location_at_pod_terminal?: string | null; + + number?: string; + + /** + * When available the pickup appointment time at the terminal is returned. + */ + pickup_appointment_at?: string | null; + + /** + * The last free day for pickup before demmurage accrues. Corresponding timezone is + * pod_timezone. + */ + pickup_lfd?: string | null; + + /** + * Time the vessel arrived at the POD + */ + pod_arrived_at?: string | null; + + /** + * Discharge time at the port of discharge + */ + pod_discharged_at?: string | null; + + /** + * Full Out time at port of discharge. Null for inland moves. + */ + pod_full_out_at?: string | null; + + /** + * The chassis number used when container was picked up at POD (if available) + */ + pod_full_out_chassis_number?: string | null; + + pod_last_tracking_request_at?: string | null; + + /** + * The SCAC of the rail carrier for the pickup leg of the container's + * journey.(BETA) + */ + pod_rail_carrier_scac?: string | null; + + pod_rail_departed_at?: string | null; + + pod_rail_loaded_at?: string | null; + + /** + * IANA tz. Applies to attributes pod_arrived_at, pod_discharged_at, + * pickup_appointment_at, pod_full_out_at. + */ + pod_timezone?: string | null; + + ref_numbers?: Array; + + seal_number?: string | null; + + shipment_last_tracking_request_at?: string | null; + + /** + * When the terminal was last checked. + */ + terminal_checked_at?: string | null; + + weight_in_lbs?: number | null; + } + + export namespace Attributes { + export interface FeesAtPodTerminal { + /** + * The fee amount in local currency + */ + amount: number; + + type: 'demurrage' | 'exam' | 'extended_dwell_time' | 'other' | 'total'; + + /** + * The ISO 4217 currency code of the fee is charged in. E.g. USD + */ + currency_code?: string; + } + + export interface HoldsAtPodTerminal { + name: string; + + status: 'pending' | 'hold'; + + /** + * Text description from the terminal (if any) + */ + description?: string | null; + } + } + + export interface Relationships { + pickup_facility?: Relationships.PickupFacility; + + pod_terminal?: Relationships.PodTerminal; + + raw_events?: Relationships.RawEvents; + + shipment?: Relationships.Shipment; + + transport_events?: Relationships.TransportEvents; + } + + export namespace Relationships { + export interface PickupFacility { + data?: PickupFacility.Data; + } + + export namespace PickupFacility { + export interface Data { + id?: string; + + type?: 'terminal'; + } + } + + export interface PodTerminal { + data?: PodTerminal.Data; + } + + export namespace PodTerminal { + export interface Data { + id?: string; + + type?: 'terminal'; + } + } + + export interface RawEvents { + data?: Array; + } + + export namespace RawEvents { + export interface Data { + id?: string; + + type?: 'raw_event'; + } + } + + export interface Shipment { + data?: Shipment.Data; + } + + export namespace Shipment { + export interface Data { + id?: string; + + type?: 'shipment'; + } + } + + export interface TransportEvents { + data?: Array; + } + + export namespace TransportEvents { + export interface Data { + id?: string; + + type?: 'transport_event'; + } + } + } +} + +export interface TransportEvent { + id: string; + + type: 'transport_event'; + + attributes?: TransportEvent.Attributes; + + relationships?: TransportEvent.Relationships; +} + +export namespace TransportEvent { + export interface Attributes { + created_at?: string; + + /** + * The original source of the event data + */ + data_source?: 'shipping_line' | 'terminal' | 'ais'; + + event?: + | 'container.transport.vessel_arrived' + | 'container.transport.vessel_discharged' + | 'container.transport.vessel_loaded' + | 'container.transport.vessel_departed' + | 'container.transport.rail_departed' + | 'container.transport.rail_arrived' + | 'container.transport.rail_loaded' + | 'container.transport.rail_unloaded' + | 'container.transport.transshipment_arrived' + | 'container.transport.transshipment_discharged' + | 'container.transport.transshipment_loaded' + | 'container.transport.transshipment_departed' + | 'container.transport.feeder_arrived' + | 'container.transport.feeder_discharged' + | 'container.transport.feeder_loaded' + | 'container.transport.feeder_departed' + | 'container.transport.empty_out' + | 'container.transport.full_in' + | 'container.transport.full_out' + | 'container.transport.empty_in' + | 'container.transport.vessel_berthed' + | 'container.transport.arrived_at_inland_destination' + | 'container.transport.estimated.arrived_at_inland_destination' + | 'container.pickup_lfd.changed'; + + /** + * UNLOCODE of the event location + */ + location_locode?: string | null; + + timestamp?: string | null; + + /** + * IANA tz + */ + timezone?: string | null; + + voyage_number?: string | null; + } + + export interface Relationships { + container?: Relationships.Container; + + location?: Relationships.Location; + + shipment?: Relationships.Shipment; + + terminal?: Relationships.Terminal; + + vessel?: Relationships.Vessel; + } + + export namespace Relationships { + export interface Container { + data?: Container.Data; + } + + export namespace Container { + export interface Data { + id?: string; + + type?: 'container'; + } + } + + export interface Location { + data?: Location.Data | null; + } + + export namespace Location { + export interface Data { + id?: string; + + type?: 'port' | 'metro_area'; + } + } + + export interface Shipment { + data?: Shipment.Data; + } + + export namespace Shipment { + export interface Data { + id?: string; + + type?: 'shipment'; + } + } + + export interface Terminal { + data?: Terminal.Data | null; + } + + export namespace Terminal { + export interface Data { + id?: string; + + type?: 'terminal' | 'rail_terminal'; + } + } + + export interface Vessel { + data?: Vessel.Data | null; + } + + export namespace Vessel { + export interface Data { + id?: string; + + name?: 'vessel'; + } + } + } +} + +export interface ContainerRetrieveResponse { + /** + * Represents the equipment during a specific journey. + */ + data?: Container; + + included?: Array; +} + +export interface ContainerUpdateResponse { + /** + * Represents the equipment during a specific journey. + */ + data?: Container; +} + +export interface ContainerListResponse { + data?: Array; + + included?: Array; + + links?: ShipmentsAPI.Links; + + meta?: ShipmentsAPI.Meta; +} + +export interface ContainerGetRawEventsResponse { + data?: Array; +} + +export namespace ContainerGetRawEventsResponse { + /** + * Raw Events represent the milestones from the shipping line for a given + * container. + * + * ### About raw_event datetimes + * + * The events may include estimated future events. The event is a future event if + * an `estimated_` timestamp is not null. + * + * The datetime properties `timestamp` and `estimated`. + * + * When the `time_zone` property is present the datetimes are UTC timestamps, which + * can be converted to the local time by parsing the provided `time_zone`. + * + * When the `time_zone` property is absent, the datetimes represent local times + * which serialized as UTC timestamps for consistency. + */ + export interface Data { + id?: string; + + attributes?: Data.Attributes; + + relationships?: Data.Relationships; + + type?: 'raw_event'; + } + + export namespace Data { + export interface Attributes { + /** + * Deprecated: The datetime the event transpired in UTC + */ + actual_at?: string | null; + + /** + * Deprecated: The date of the event at the event location when no time information + * is available. + */ + actual_on?: string | null; + + /** + * When the raw_event was created in UTC + */ + created_at?: string; + + /** + * True if the timestamp is estimated, false otherwise + */ + estimated?: boolean; + + /** + * Deprecated: The estimated datetime the event will occur in UTC + */ + estimated_at?: string | null; + + /** + * Deprecated: The estimated date of the event at the event location when no time + * information is available. + */ + estimated_on?: string | null; + + /** + * Normalized string representing the event + */ + event?: + | 'empty_out' + | 'full_in' + | 'positioned_in' + | 'positioned_out' + | 'vessel_loaded' + | 'vessel_departed' + | 'transshipment_arrived' + | 'transshipment_discharged' + | 'transshipment_loaded' + | 'transshipment_departed' + | 'feeder_arrived' + | 'feeder_discharged' + | 'feeder_loaded' + | 'feeder_departed' + | 'rail_loaded' + | 'rail_departed' + | 'rail_arrived' + | 'rail_unloaded' + | 'vessel_arrived' + | 'vessel_discharged' + | 'arrived_at_destination' + | 'delivered' + | 'full_out' + | 'empty_in' + | 'vgm_received' + | 'carrier_release' + | 'customs_release' + | null; + + /** + * The order of the event. This may be helpful when only dates (i.e. actual_on) are + * available. + */ + index?: number; + + /** + * UNLOCODE of the event location + */ + location_locode?: string | null; + + /** + * The city or facility name of the event location + */ + location_name?: string; + + /** + * The event name as returned by the carrier + */ + original_event?: string; + + /** + * The datetime the event either transpired or will occur in UTC + */ + timestamp?: string; + + /** + * IANA tz where the event occured + */ + timezone?: string | null; + + /** + * The IMO of the vessel where applicable + */ + vessel_imo?: string | null; + + /** + * The name of the vessel where applicable + */ + vessel_name?: string | null; + + voyage_number?: string | null; + } + + export interface Relationships { + location?: Relationships.Location; + + vessel?: Relationships.Vessel; + } + + export namespace Relationships { + export interface Location { + data?: Location.Data; + } + + export namespace Location { + export interface Data { + id?: string; + + type?: 'port' | 'metro_area'; + } + } + + export interface Vessel { + data?: Vessel.Data; + } + + export namespace Vessel { + export interface Data { + id?: string; + + type?: 'vessel'; + } + } + } + } +} + +export interface ContainerGetTransportEventsResponse { + data?: Array; + + included?: Array< + | ShipmentsAPI.Shipment + | Container + | PortsAPI.Port + | MetroAreasAPI.MetroArea + | TerminalsAPI.Terminal + | ContainerGetTransportEventsResponse.RailTerminal + | VesselsAPI.Vessel + >; + + links?: ShipmentsAPI.Links; + + meta?: ShipmentsAPI.Meta; +} + +export namespace ContainerGetTransportEventsResponse { + export interface RailTerminal { + attributes: RailTerminal.Attributes; + + id?: string; + + relationships?: RailTerminal.Relationships; + + type?: 'rail_terminal'; + } + + export namespace RailTerminal { + export interface Attributes { + name: string; + + /** + * CBP FIRMS Code or CBS Sublocation Code + */ + firms_code?: string; + + nickname?: string; + } + + export interface Relationships { + metro_area?: Relationships.MetroArea; + + port?: Relationships.Port; + } + + export namespace Relationships { + export interface MetroArea { + data?: MetroArea.Data; + } + + export namespace MetroArea { + export interface Data { + id?: string; + + type?: 'metro_area'; + } + } + + export interface Port { + data?: Port.Data | null; + } + + export namespace Port { + export interface Data { + id?: string; + + type?: 'port'; + } + } + } + } +} + +export interface ContainerRetrieveParams { + /** + * Comma delimited list of relations to include + */ + include?: string; +} + +export interface ContainerUpdateParams { + data?: ContainerUpdateParams.Data; +} + +export namespace ContainerUpdateParams { + export interface Data { + attributes: Data.Attributes; + } + + export namespace Data { + export interface Attributes { + ref_numbers?: Array; + } + } +} + +export interface ContainerListParams { + /** + * Comma delimited list of relations to include + */ + include?: string; + + 'page[number]'?: number; + + 'page[size]'?: number; + + /** + * Number of seconds in which containers were refreshed + */ + terminal_checked_before?: number; +} + +export interface ContainerGetTransportEventsParams { + /** + * Comma delimited list of relations to include + */ + include?: string; +} + +export declare namespace Containers { + export { + type Container as Container, + type TransportEvent as TransportEvent, + type ContainerRetrieveResponse as ContainerRetrieveResponse, + type ContainerUpdateResponse as ContainerUpdateResponse, + type ContainerListResponse as ContainerListResponse, + type ContainerGetRawEventsResponse as ContainerGetRawEventsResponse, + type ContainerGetTransportEventsResponse as ContainerGetTransportEventsResponse, + type ContainerRetrieveParams as ContainerRetrieveParams, + type ContainerUpdateParams as ContainerUpdateParams, + type ContainerListParams as ContainerListParams, + type ContainerGetTransportEventsParams as ContainerGetTransportEventsParams, + }; +} diff --git a/src/resources/index.ts b/src/resources/index.ts new file mode 100644 index 00000000..59eea0ca --- /dev/null +++ b/src/resources/index.ts @@ -0,0 +1,93 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export { + Containers, + type Container, + type TransportEvent, + type ContainerRetrieveResponse, + type ContainerUpdateResponse, + type ContainerListResponse, + type ContainerGetRawEventsResponse, + type ContainerGetTransportEventsResponse, + type ContainerRetrieveParams, + type ContainerUpdateParams, + type ContainerListParams, + type ContainerGetTransportEventsParams, +} from './containers'; +export { MetroAreas, type MetroArea, type MetroAreaRetrieveResponse } from './metro-areas'; +export { + Parties, + type LinkSelf, + type Party, + type PartyCreateResponse, + type PartyRetrieveResponse, + type PartyUpdateResponse, + type PartyListResponse, + type PartyCreateParams, + type PartyUpdateParams, + type PartyListParams, +} from './parties'; +export { Ports, type Port, type PortRetrieveResponse } from './ports'; +export { + Shipments, + type Links, + type Meta, + type Shipment, + type ShipmentRetrieveResponse, + type ShipmentUpdateResponse, + type ShipmentListResponse, + type ShipmentResumeTrackingResponse, + type ShipmentStopTrackingResponse, + type ShipmentRetrieveParams, + type ShipmentUpdateParams, + type ShipmentListParams, +} from './shipments'; +export { + ShippingLines, + type ShippingLine, + type ShippingLineRetrieveResponse, + type ShippingLineListResponse, +} from './shipping-lines'; +export { Terminals, type Terminal, type TerminalRetrieveResponse } from './terminals'; +export { + TrackingRequests, + type Account, + type TrackingRequest, + type TrackingRequestCreateResponse, + type TrackingRequestRetrieveResponse, + type TrackingRequestUpdateResponse, + type TrackingRequestListResponse, + type TrackingRequestCreateParams, + type TrackingRequestRetrieveParams, + type TrackingRequestUpdateParams, + type TrackingRequestListParams, +} from './tracking-requests'; +export { + Vessels, + type Vessel, + type VesselRetrieveByIDResponse, + type VesselRetrieveByImoResponse, +} from './vessels'; +export { + WebhookNotifications, + type EstimatedEvent, + type WebhookNotification, + type WebhookNotificationRetrieveResponse, + type WebhookNotificationListResponse, + type WebhookNotificationGetExamplesResponse, + type WebhookNotificationRetrieveParams, + type WebhookNotificationListParams, + type WebhookNotificationGetExamplesParams, +} from './webhook-notifications'; +export { + Webhooks, + type Webhook, + type WebhookCreateResponse, + type WebhookRetrieveResponse, + type WebhookUpdateResponse, + type WebhookListResponse, + type WebhookListIPsResponse, + type WebhookCreateParams, + type WebhookUpdateParams, + type WebhookListParams, +} from './webhooks'; diff --git a/src/resources/metro-areas.ts b/src/resources/metro-areas.ts new file mode 100644 index 00000000..f7c1af6c --- /dev/null +++ b/src/resources/metro-areas.ts @@ -0,0 +1,55 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; +import { path } from '../internal/utils/path'; + +export class MetroAreas extends APIResource { + /** + * Return the details of a single metro area. + */ + retrieve(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/metro_areas/${id}`, options); + } +} + +export interface MetroArea { + id: string; + + type: 'metro_area'; + + attributes?: MetroArea.Attributes; +} + +export namespace MetroArea { + export interface Attributes { + /** + * UN/LOCODE + */ + code?: string; + + country_code?: string; + + latitude?: number | null; + + longitude?: number | null; + + name?: string; + + state_abbr?: string | null; + + /** + * IANA tz + */ + time_zone?: string; + } +} + +export interface MetroAreaRetrieveResponse { + data?: MetroArea; +} + +export declare namespace MetroAreas { + export { type MetroArea as MetroArea, type MetroAreaRetrieveResponse as MetroAreaRetrieveResponse }; +} diff --git a/src/resources/parties.ts b/src/resources/parties.ts new file mode 100644 index 00000000..dc4ff128 --- /dev/null +++ b/src/resources/parties.ts @@ -0,0 +1,152 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import * as ShipmentsAPI from './shipments'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; +import { path } from '../internal/utils/path'; + +export class Parties extends APIResource { + /** + * Creates a new party + */ + create( + body: PartyCreateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.post('/parties', { body, ...options }); + } + + /** + * Returns a party by it's given identifier + */ + retrieve(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/parties/${id}`, options); + } + + /** + * Updates a party + */ + update( + id: string, + body: PartyUpdateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.patch(path`/parties/${id}`, { body, ...options }); + } + + /** + * Get a list of parties + */ + list( + query: PartyListParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get('/parties', { query, ...options }); + } +} + +export interface LinkSelf { + self?: string; +} + +export interface Party { + attributes: Party.Attributes; + + id?: string; + + type?: 'party'; +} + +export namespace Party { + export interface Attributes { + /** + * Company name + */ + company_name: string; + } +} + +export interface PartyCreateResponse { + data?: Party; + + links?: LinkSelf; +} + +export interface PartyRetrieveResponse { + data?: Party; + + links?: LinkSelf; +} + +export interface PartyUpdateResponse { + data?: Party; + + links?: LinkSelf; +} + +export interface PartyListResponse { + data?: Array; + + links?: ShipmentsAPI.Links; + + meta?: ShipmentsAPI.Meta; +} + +export interface PartyCreateParams { + data?: PartyCreateParams.Data; +} + +export namespace PartyCreateParams { + export interface Data { + attributes: Data.Attributes; + } + + export namespace Data { + export interface Attributes { + /** + * The name of the company + */ + company_name?: string; + } + } +} + +export interface PartyUpdateParams { + data?: PartyUpdateParams.Data; +} + +export namespace PartyUpdateParams { + export interface Data { + attributes: Data.Attributes; + } + + export namespace Data { + export interface Attributes { + /** + * The name of the company + */ + company_name?: string; + } + } +} + +export interface PartyListParams { + 'page[number]'?: number; + + 'page[size]'?: number; +} + +export declare namespace Parties { + export { + type LinkSelf as LinkSelf, + type Party as Party, + type PartyCreateResponse as PartyCreateResponse, + type PartyRetrieveResponse as PartyRetrieveResponse, + type PartyUpdateResponse as PartyUpdateResponse, + type PartyListResponse as PartyListResponse, + type PartyCreateParams as PartyCreateParams, + type PartyUpdateParams as PartyUpdateParams, + type PartyListParams as PartyListParams, + }; +} diff --git a/src/resources/ports.ts b/src/resources/ports.ts new file mode 100644 index 00000000..b98b160b --- /dev/null +++ b/src/resources/ports.ts @@ -0,0 +1,60 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; +import { path } from '../internal/utils/path'; + +export class Ports extends APIResource { + /** + * Return the details of a single port. + */ + retrieve(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/ports/${id}`, options); + } +} + +export interface Port { + id: string; + + type: 'port'; + + attributes?: Port.Attributes; +} + +export namespace Port { + export interface Attributes { + city?: string | null; + + /** + * UN/LOCODE + */ + code?: string; + + /** + * 2 digit country code + */ + country_code?: string; + + latitude?: number | null; + + longitude?: number | null; + + name?: string; + + state_abbr?: string | null; + + /** + * IANA tz + */ + time_zone?: string; + } +} + +export interface PortRetrieveResponse { + data?: Port; +} + +export declare namespace Ports { + export { type Port as Port, type PortRetrieveResponse as PortRetrieveResponse }; +} diff --git a/src/resources/shipments.ts b/src/resources/shipments.ts new file mode 100644 index 00000000..9c9110f9 --- /dev/null +++ b/src/resources/shipments.ts @@ -0,0 +1,413 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import * as ContainersAPI from './containers'; +import * as PortsAPI from './ports'; +import * as TerminalsAPI from './terminals'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; +import { path } from '../internal/utils/path'; + +export class Shipments extends APIResource { + /** + * Retrieves the details of an existing shipment. You need only supply the unique + * shipment `id` that was returned upon `tracking_request` creation. + */ + retrieve( + id: string, + query: ShipmentRetrieveParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get(path`/shipments/${id}`, { query, ...options }); + } + + /** + * Update a shipment + */ + update( + id: string, + body: ShipmentUpdateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.patch(path`/shipments/${id}`, { body, ...options }); + } + + /** + * Returns a list of your shipments. The shipments are returned sorted by creation + * date, with the most recent shipments appearing first. + * + * This api will return all shipments associated with the account. Shipments + * created via the `tracking_request` API aswell as the ones added via the + * dashboard will be retuned via this endpoint. + */ + list( + query: ShipmentListParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get('/shipments', { query, ...options }); + } + + /** + * Resume tracking a shipment. Keep in mind that some information is only made + * available by our data sources at specific times, so a stopped and resumed + * shipment may have some information missing. + */ + resumeTracking(id: string, options?: RequestOptions): APIPromise { + return this._client.patch(path`/shipments/${id}/resume_tracking`, options); + } + + /** + * We'll stop tracking the shipment, which means that there will be no more + * updates. You can still access the shipment's previously-collected information + * via the API or dashboard. + * + * You can resume tracking a shipment by calling the `resume_tracking` endpoint, + * but keep in mind that some information is only made available by our data + * sources at specific times, so a stopped and resumed shipment may have some + * information missing. + */ + stopTracking(id: string, options?: RequestOptions): APIPromise { + return this._client.patch(path`/shipments/${id}/stop_tracking`, options); + } +} + +export interface Links { + first?: string; + + last?: string; + + next?: string; + + prev?: string; + + self?: string; +} + +export interface Meta { + size?: number; + + total?: number; +} + +export interface Shipment { + id: string; + + attributes: Shipment.Attributes; + + links: Shipment.Links; + + relationships: Shipment.Relationships; + + type: 'shipment'; +} + +export namespace Shipment { + export interface Attributes { + bill_of_lading_number: string; + + created_at?: string; + + customer_name?: string | null; + + destination_ata_at?: string | null; + + destination_eta_at?: string | null; + + /** + * UN/LOCODE + */ + destination_locode?: string | null; + + destination_name?: string | null; + + /** + * IANA tz + */ + destination_timezone?: string | null; + + /** + * When Terminal49 last tried to update the shipment status from the shipping line + */ + line_tracking_last_attempted_at?: string | null; + + /** + * When Terminal49 last successfully updated the shipment status from the shipping + * line + */ + line_tracking_last_succeeded_at?: string | null; + + /** + * When Terminal49 stopped checking at the shipping line + */ + line_tracking_stopped_at?: string | null; + + /** + * The reason Terminal49 stopped checking + */ + line_tracking_stopped_reason?: + | 'all_containers_terminated' + | 'past_arrival_window' + | 'no_updates_at_line' + | 'cancelled_by_user' + | 'booking_cancelled' + | null; + + /** + * The normalized version of the shipment number used for querying the carrier + */ + normalized_number?: string; + + pod_ata_at?: string | null; + + pod_eta_at?: string | null; + + pod_original_eta_at?: string | null; + + /** + * IANA tz + */ + pod_timezone?: string | null; + + pod_vessel_imo?: string | null; + + pod_vessel_name?: string | null; + + pod_voyage_number?: string | null; + + pol_atd_at?: string | null; + + pol_etd_at?: string | null; + + /** + * IANA tz + */ + pol_timezone?: string | null; + + /** + * UN/LOCODE + */ + port_of_discharge_locode?: string | null; + + port_of_discharge_name?: string | null; + + /** + * UN/LOCODE + */ + port_of_lading_locode?: string | null; + + port_of_lading_name?: string | null; + + ref_numbers?: Array | null; + + shipping_line_name?: string; + + shipping_line_scac?: string; + + shipping_line_short_name?: string; + + tags?: Array; + } + + export interface Links { + self: string; + } + + export interface Relationships { + containers?: Relationships.Containers; + + destination?: Relationships.Destination; + + destination_terminal?: Relationships.DestinationTerminal; + + line_tracking_stopped_by_user?: Relationships.LineTrackingStoppedByUser; + + pod_terminal?: Relationships.PodTerminal; + + port_of_discharge?: Relationships.PortOfDischarge; + + port_of_lading?: Relationships.PortOfLading; + } + + export namespace Relationships { + export interface Containers { + data?: Array; + } + + export namespace Containers { + export interface Data { + id: string; + + type: 'container'; + } + } + + export interface Destination { + data?: Destination.Data | null; + } + + export namespace Destination { + export interface Data { + id: string; + + type: 'port' | 'metro_area'; + } + } + + export interface DestinationTerminal { + data?: DestinationTerminal.Data; + } + + export namespace DestinationTerminal { + export interface Data { + id: string; + + type: 'terminal' | 'rail_terminal'; + } + } + + export interface LineTrackingStoppedByUser { + data?: LineTrackingStoppedByUser.Data; + } + + export namespace LineTrackingStoppedByUser { + export interface Data { + id: string; + + type: 'user'; + } + } + + export interface PodTerminal { + data?: PodTerminal.Data; + } + + export namespace PodTerminal { + export interface Data { + id: string; + + type: 'terminal'; + } + } + + export interface PortOfDischarge { + data?: PortOfDischarge.Data | null; + } + + export namespace PortOfDischarge { + export interface Data { + id: string; + + type: 'port'; + } + } + + export interface PortOfLading { + data?: PortOfLading.Data | null; + } + + export namespace PortOfLading { + export interface Data { + id: string; + + type: 'port'; + } + } + } +} + +export interface ShipmentRetrieveResponse { + data?: Shipment; + + included?: Array; +} + +export interface ShipmentUpdateResponse { + data?: Shipment; +} + +export interface ShipmentListResponse { + data?: Array; + + included?: Array; + + links?: Links; + + meta?: Meta; +} + +export interface ShipmentResumeTrackingResponse { + data?: Shipment; +} + +export interface ShipmentStopTrackingResponse { + data?: Shipment; +} + +export interface ShipmentRetrieveParams { + /** + * Comma delimited list of relations to include + */ + include?: string; +} + +export interface ShipmentUpdateParams { + data?: ShipmentUpdateParams.Data; +} + +export namespace ShipmentUpdateParams { + export interface Data { + attributes: Data.Attributes; + } + + export namespace Data { + export interface Attributes { + /** + * Shipment ref numbers. + */ + ref_numbers?: Array; + + /** + * Tags related to a shipment + */ + shipment_tags?: Array; + } + } +} + +export interface ShipmentListParams { + /** + * Comma delimited list of relations to include + */ + include?: string; + + /** + * Search shipments by the original request tracking `request_number` + */ + number?: string; + + 'page[number]'?: number; + + 'page[size]'?: number; + + /** + * Search shipments by master bill of lading, reference number, or container + * number. + */ + q?: string; +} + +export declare namespace Shipments { + export { + type Links as Links, + type Meta as Meta, + type Shipment as Shipment, + type ShipmentRetrieveResponse as ShipmentRetrieveResponse, + type ShipmentUpdateResponse as ShipmentUpdateResponse, + type ShipmentListResponse as ShipmentListResponse, + type ShipmentResumeTrackingResponse as ShipmentResumeTrackingResponse, + type ShipmentStopTrackingResponse as ShipmentStopTrackingResponse, + type ShipmentRetrieveParams as ShipmentRetrieveParams, + type ShipmentUpdateParams as ShipmentUpdateParams, + type ShipmentListParams as ShipmentListParams, + }; +} diff --git a/src/resources/shipping-lines.ts b/src/resources/shipping-lines.ts new file mode 100644 index 00000000..7384b241 --- /dev/null +++ b/src/resources/shipping-lines.ts @@ -0,0 +1,71 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import * as ShipmentsAPI from './shipments'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; +import { path } from '../internal/utils/path'; + +export class ShippingLines extends APIResource { + /** + * Return the details of a single shipping line. + */ + retrieve(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/shipping_lines/${id}`, options); + } + + /** + * Return a list of shipping lines supported by Terminal49. N.B. There is no + * pagination for this endpoint. + */ + list(options?: RequestOptions): APIPromise { + return this._client.get('/shipping_lines', options); + } +} + +export interface ShippingLine { + id: string; + + attributes: ShippingLine.Attributes; + + type: 'shipping_line'; +} + +export namespace ShippingLine { + export interface Attributes { + /** + * Additional SCACs which will be accepted in tracking requests + */ + alternative_scacs: Array; + + bill_of_lading_tracking_support: boolean; + + booking_number_tracking_support: boolean; + + container_number_tracking_support: boolean; + + name: string; + + scac: string; + + short_name: string; + } +} + +export interface ShippingLineRetrieveResponse { + data?: ShippingLine; +} + +export interface ShippingLineListResponse { + data?: Array; + + links?: ShipmentsAPI.Links; +} + +export declare namespace ShippingLines { + export { + type ShippingLine as ShippingLine, + type ShippingLineRetrieveResponse as ShippingLineRetrieveResponse, + type ShippingLineListResponse as ShippingLineListResponse, + }; +} diff --git a/src/resources/terminals.ts b/src/resources/terminals.ts new file mode 100644 index 00000000..2c75ee10 --- /dev/null +++ b/src/resources/terminals.ts @@ -0,0 +1,104 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; +import { path } from '../internal/utils/path'; + +export class Terminals extends APIResource { + /** + * Return the details of a single terminal. + */ + retrieve(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/terminals/${id}`, options); + } +} + +export interface Terminal { + attributes: Terminal.Attributes; + + relationships: Terminal.Relationships; + + id?: string; + + type?: 'terminal'; +} + +export namespace Terminal { + export interface Attributes { + name: string; + + /** + * BIC Facility Code + */ + bic_facility_code?: string; + + /** + * City part of the address + */ + city?: string; + + /** + * Country part of the address + */ + country?: string; + + /** + * CBP FIRMS Code or CBS Sublocation Code + */ + firms_code?: string; + + nickname?: string; + + /** + * SMDG Code + */ + smdg_code?: string; + + /** + * State part of the address + */ + state?: string; + + /** + * State abbreviation for the state + */ + state_abbr?: string; + + /** + * Street part of the address + */ + street?: string; + + /** + * ZIP code part of the address + */ + zip?: string; + } + + export interface Relationships { + port: Relationships.Port; + } + + export namespace Relationships { + export interface Port { + data?: Port.Data; + } + + export namespace Port { + export interface Data { + id?: string; + + type?: 'port'; + } + } + } +} + +export interface TerminalRetrieveResponse { + data?: Terminal; +} + +export declare namespace Terminals { + export { type Terminal as Terminal, type TerminalRetrieveResponse as TerminalRetrieveResponse }; +} diff --git a/src/resources/tracking-requests.ts b/src/resources/tracking-requests.ts new file mode 100644 index 00000000..aea6bd49 --- /dev/null +++ b/src/resources/tracking-requests.ts @@ -0,0 +1,394 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import * as ShipmentsAPI from './shipments'; +import * as ShippingLinesAPI from './shipping-lines'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; +import { path } from '../internal/utils/path'; + +export class TrackingRequests extends APIResource { + /** + * To track an ocean shipment, you create a new tracking request. Two attributes + * are required to track a shipment. A `bill of lading/booking number` and a + * shipping line `SCAC`. + * + * Once a tracking request is created we will attempt to fetch the shipment details + * and it's related containers from the shipping line. If the attempt is successful + * we will create in new shipment object including any related container objects. + * We will send a `tracking_request.succeeded` webhook notification to your + * webhooks. + * + * If the attempt to fetch fails then we will send a `tracking_request.failed` + * webhook notification to your `webhooks`. + * + * A `tracking_request.succeeded` or `tracking_request.failed` webhook notificaiton + * will only be sent if you have atleast one active webhook. + * + * @example + * ```ts + * const trackingRequest = + * await client.trackingRequests.create({ + * data: { + * attributes: { + * request_type: 'bill_of_lading', + * request_number: 'MEDUFR030802', + * ref_numbers: ['PO12345', 'HBL12345', 'CUSREF1234'], + * shipment_tags: ['camembert'], + * scac: 'MSCU', + * }, + * relationships: { + * customer: { + * data: { + * id: 'f7cb530a-9e60-412c-a5bc-205a2f34ba54', + * type: 'party', + * }, + * }, + * }, + * type: 'tracking_request', + * }, + * }); + * ``` + */ + create( + body: TrackingRequestCreateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.post('/tracking_requests', { body, ...options }); + } + + /** + * Get the details and status of an existing tracking request. + * + * @example + * ```ts + * const trackingRequest = + * await client.trackingRequests.retrieve('id'); + * ``` + */ + retrieve( + id: string, + query: TrackingRequestRetrieveParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get(path`/tracking_requests/${id}`, { query, ...options }); + } + + /** + * Update a tracking request + * + * @example + * ```ts + * const trackingRequest = + * await client.trackingRequests.update('id'); + * ``` + */ + update( + id: string, + body: TrackingRequestUpdateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.patch(path`/tracking_requests/${id}`, { body, ...options }); + } + + /** + * Returns a list of your tracking requests. The tracking requests are returned + * sorted by creation date, with the most recent tracking request appearing first. + * + * @example + * ```ts + * const trackingRequests = + * await client.trackingRequests.list(); + * ``` + */ + list( + query: TrackingRequestListParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get('/tracking_requests', { query, ...options }); + } +} + +export interface Account { + id: string; + + attributes: Account.Attributes; + + type: 'container'; +} + +export namespace Account { + export interface Attributes { + company_name: string; + } +} + +export interface TrackingRequest { + id: string; + + type: 'tracking_request'; + + attributes?: TrackingRequest.Attributes; + + relationships?: TrackingRequest.Relationships; +} + +export namespace TrackingRequest { + export interface Attributes { + created_at: string; + + request_number: string; + + request_type: 'bill_of_lading' | 'booking_number' | 'container'; + + scac: string; + + status: 'pending' | 'awaiting_manifest' | 'created' | 'failed' | 'tracking_stopped'; + + /** + * If the tracking request has failed, or is currently failing, the last reason we + * were unable to complete the request + */ + failed_reason?: + | 'booking_cancelled' + | 'duplicate' + | 'expired' + | 'internal_processing_error' + | 'invalid_number' + | 'not_found' + | 'retries_exhausted' + | 'shipping_line_unreachable' + | 'unrecognized_response' + | 'data_unavailable' + | null; + + is_retrying?: boolean; + + ref_numbers?: Array | null; + + /** + * How many times T49 has attempted to get the shipment from the shipping line + */ + retry_count?: number | null; + + tags?: Array; + + updated_at?: string; + } + + export interface Relationships { + customer?: Relationships.Customer; + + tracked_object?: Relationships.TrackedObject; + } + + export namespace Relationships { + export interface Customer { + data?: Customer.Data; + } + + export namespace Customer { + export interface Data { + id?: string; + + type?: 'party'; + } + } + + export interface TrackedObject { + data?: TrackedObject.Data | null; + } + + export namespace TrackedObject { + export interface Data { + id?: string; + + type?: 'shipment'; + } + } + } +} + +export interface TrackingRequestCreateResponse { + data?: TrackingRequest; + + included?: Array; +} + +export interface TrackingRequestRetrieveResponse { + data?: TrackingRequest; + + included?: Array; +} + +export interface TrackingRequestUpdateResponse { + data?: TrackingRequest; +} + +export interface TrackingRequestListResponse { + data?: Array; + + included?: Array; + + links?: ShipmentsAPI.Links; + + meta?: ShipmentsAPI.Meta; +} + +export namespace TrackingRequestListResponse { + export interface UnionMember2 { + id?: string; + + links?: UnionMember2.Links; + + type?: 'shipment'; + } + + export namespace UnionMember2 { + export interface Links { + self?: string; + } + } +} + +export interface TrackingRequestCreateParams { + data?: TrackingRequestCreateParams.Data; +} + +export namespace TrackingRequestCreateParams { + export interface Data { + type: 'tracking_request'; + + attributes?: Data.Attributes; + + relationships?: Data.Relationships; + } + + export namespace Data { + export interface Attributes { + request_number: string; + + /** + * The type of document number to be supplied. Container number support is + * currently in BETA. + */ + request_type: 'bill_of_lading' | 'booking_number' | 'container'; + + scac: string; + + /** + * Optional list of reference numbers to be added to the shipment when tracking + * request completes + */ + ref_numbers?: Array; + + /** + * Optional list of tags to be added to the shipment when tracking request + * completes + */ + shipment_tags?: Array; + } + + export interface Relationships { + customer?: Relationships.Customer; + } + + export namespace Relationships { + export interface Customer { + data?: Customer.Data; + } + + export namespace Customer { + export interface Data { + id?: string; + + type?: 'party'; + } + } + } + } +} + +export interface TrackingRequestRetrieveParams { + /** + * Comma delimited list of relations to include. 'tracked_object' is included by + * default. + */ + include?: string; +} + +export interface TrackingRequestUpdateParams { + data?: TrackingRequestUpdateParams.Data; +} + +export namespace TrackingRequestUpdateParams { + export interface Data { + attributes: Data.Attributes; + } + + export namespace Data { + export interface Attributes { + /** + * Tracking request ref number. + */ + ref_number?: string; + } + } +} + +export interface TrackingRequestListParams { + /** + * filter by tracking_requests `created_at` before a certain ISO8601 timestamp + */ + 'filter[created_at][end]'?: string; + + /** + * filter by tracking_requests `created_at` after a certain ISO8601 timestamp + */ + 'filter[created_at][start]'?: string; + + /** + * filter by `request_number` + */ + 'filter[request_number]'?: string; + + /** + * filter by shipping line `scac` + */ + 'filter[scac]'?: string; + + /** + * filter by `status` + */ + 'filter[status]'?: 'created' | 'pending' | 'failed'; + + /** + * Comma delimited list of relations to include. 'tracked_object' is included by + * default. + */ + include?: string; + + 'page[number]'?: number; + + 'page[size]'?: number; + + /** + * A search term to be applied against request_number and reference_numbers. + */ + q?: string; +} + +export declare namespace TrackingRequests { + export { + type Account as Account, + type TrackingRequest as TrackingRequest, + type TrackingRequestCreateResponse as TrackingRequestCreateResponse, + type TrackingRequestRetrieveResponse as TrackingRequestRetrieveResponse, + type TrackingRequestUpdateResponse as TrackingRequestUpdateResponse, + type TrackingRequestListResponse as TrackingRequestListResponse, + type TrackingRequestCreateParams as TrackingRequestCreateParams, + type TrackingRequestRetrieveParams as TrackingRequestRetrieveParams, + type TrackingRequestUpdateParams as TrackingRequestUpdateParams, + type TrackingRequestListParams as TrackingRequestListParams, + }; +} diff --git a/src/resources/vessels.ts b/src/resources/vessels.ts new file mode 100644 index 00000000..cd0547fb --- /dev/null +++ b/src/resources/vessels.ts @@ -0,0 +1,92 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; +import { path } from '../internal/utils/path'; + +export class Vessels extends APIResource { + /** + * Returns a vessel by it's given identifier + */ + retrieveByID(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/vessels/${id}`, options); + } + + /** + * Returns a vessel by the given IMO number. + */ + retrieveByImo(imo: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/vessels/${imo}`, options); + } +} + +export interface Vessel { + id?: string; + + attributes?: Vessel.Attributes; + + type?: 'vessel'; +} + +export namespace Vessel { + export interface Attributes { + /** + * International Maritime Organization (IMO) number + */ + imo?: string | null; + + /** + * The current latitude position of the vessel + */ + latitude?: number | null; + + /** + * The current longitude position of the vessel + */ + longitude?: number | null; + + /** + * Maritime Mobile Service Identity (MMSI) + */ + mmsi?: string | null; + + /** + * The name of the ship or vessel + */ + name?: string; + + /** + * The current speed of the ship in knots (nautical miles per hour) + */ + nautical_speed_knots?: number | null; + + /** + * The current heading of the ship in degrees, where 0 is North, 90 is East, 180 is + * South, and 270 is West + */ + navigational_heading_degrees?: number | null; + + /** + * The timestamp of when the ship's position was last recorded, in ISO 8601 date + * and time format + */ + position_timestamp?: string | null; + } +} + +export interface VesselRetrieveByIDResponse { + data?: Vessel; +} + +export interface VesselRetrieveByImoResponse { + data?: Vessel; +} + +export declare namespace Vessels { + export { + type Vessel as Vessel, + type VesselRetrieveByIDResponse as VesselRetrieveByIDResponse, + type VesselRetrieveByImoResponse as VesselRetrieveByImoResponse, + }; +} diff --git a/src/resources/webhook-notifications.ts b/src/resources/webhook-notifications.ts new file mode 100644 index 00000000..de32c82e --- /dev/null +++ b/src/resources/webhook-notifications.ts @@ -0,0 +1,393 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import * as ContainersAPI from './containers'; +import * as ShipmentsAPI from './shipments'; +import * as TrackingRequestsAPI from './tracking-requests'; +import * as WebhooksAPI from './webhooks'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; +import { path } from '../internal/utils/path'; + +export class WebhookNotifications extends APIResource { + retrieve( + id: string, + query: WebhookNotificationRetrieveParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get(path`/webhook_notifications/${id}`, { query, ...options }); + } + + /** + * Return the list of webhook notifications. This can be useful for reconciling + * your data if your endpoint has been down. + */ + list( + query: WebhookNotificationListParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get('/webhook_notifications', { query, ...options }); + } + + /** + * Returns an example payload as it would be sent to a webhook endpoint for the + * provided `event` + */ + getExamples( + query: WebhookNotificationGetExamplesParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get('/webhook_notifications/examples', { query, ...options }); + } +} + +export interface EstimatedEvent { + id: string; + + attributes: EstimatedEvent.Attributes; + + relationships: EstimatedEvent.Relationships; + + type: 'estimated_event'; +} + +export namespace EstimatedEvent { + export interface Attributes { + /** + * When the estimated event was created + */ + created_at: string; + + estimated_timestamp: string; + + event: 'shipment.estimated.arrival'; + + /** + * The original source of the event data + */ + data_source?: 'shipping_line' | 'terminal'; + + /** + * UNLOCODE of the event location + */ + location_locode?: string | null; + + /** + * IANA tz + */ + timezone?: string | null; + + voyage_number?: string | null; + } + + export interface Relationships { + shipment: Relationships.Shipment; + + port?: Relationships.Port; + + vessel?: Relationships.Vessel; + } + + export namespace Relationships { + export interface Shipment { + data: Shipment.Data; + } + + export namespace Shipment { + export interface Data { + id: string; + + type: 'shipment'; + } + } + + export interface Port { + data?: Port.Data | null; + } + + export namespace Port { + export interface Data { + id?: string; + + type?: 'port'; + } + } + + export interface Vessel { + data?: Vessel.Data | null; + } + + export namespace Vessel { + export interface Data { + id?: string; + + type?: 'vessel'; + } + } + } +} + +export interface WebhookNotification { + id?: string; + + attributes?: WebhookNotification.Attributes; + + relationships?: WebhookNotification.Relationships; + + type?: 'webhook_notification'; +} + +export namespace WebhookNotification { + export interface Attributes { + created_at: string; + + /** + * Whether the notification has been delivered to the webhook endpoint + */ + delivery_status: 'pending' | 'succeeded' | 'failed'; + + event: + | 'container.transport.vessel_arrived' + | 'container.transport.vessel_discharged' + | 'container.transport.vessel_loaded' + | 'container.transport.vessel_departed' + | 'container.transport.rail_departed' + | 'container.transport.rail_arrived' + | 'container.transport.rail_loaded' + | 'container.transport.rail_unloaded' + | 'container.transport.transshipment_arrived' + | 'container.transport.transshipment_discharged' + | 'container.transport.transshipment_loaded' + | 'container.transport.transshipment_departed' + | 'container.transport.feeder_arrived' + | 'container.transport.feeder_discharged' + | 'container.transport.feeder_loaded' + | 'container.transport.feeder_departed' + | 'container.transport.empty_out' + | 'container.transport.full_in' + | 'container.transport.full_out' + | 'container.transport.empty_in' + | 'container.transport.vessel_berthed' + | 'shipment.estimated.arrival' + | 'tracking_request.succeeded' + | 'tracking_request.failed' + | 'tracking_request.awaiting_manifest' + | 'tracking_request.tracking_stopped' + | 'container.created' + | 'container.updated' + | 'container.pod_terminal_changed' + | 'container.transport.arrived_at_inland_destination' + | 'container.transport.estimated.arrived_at_inland_destination' + | 'container.pickup_lfd.changed'; + } + + export interface Relationships { + webhook: Relationships.Webhook; + + reference_object?: Relationships.ReferenceObject; + } + + export namespace Relationships { + export interface Webhook { + data?: Webhook.Data; + } + + export namespace Webhook { + export interface Data { + id?: string; + + type?: 'webhook'; + } + } + + export interface ReferenceObject { + data?: ReferenceObject.Data; + } + + export namespace ReferenceObject { + export interface Data { + id?: string; + + type?: 'tracking_request' | 'estimated_event' | 'transport_event' | 'container_updated_event'; + } + } + } +} + +export interface WebhookNotificationRetrieveResponse { + data?: WebhookNotification; + + included?: Array< + | WebhooksAPI.Webhook + | TrackingRequestsAPI.TrackingRequest + | ContainersAPI.TransportEvent + | EstimatedEvent + | WebhookNotificationRetrieveResponse.ContainerUpdatedEvent + >; +} + +export namespace WebhookNotificationRetrieveResponse { + export interface ContainerUpdatedEvent { + relationships: ContainerUpdatedEvent.Relationships; + + id?: string; + + attributes?: ContainerUpdatedEvent.Attributes; + + type?: string; + } + + export namespace ContainerUpdatedEvent { + export interface Relationships { + container: Relationships.Container; + + terminal: Relationships.Terminal; + } + + export namespace Relationships { + export interface Container { + data: Container.Data; + } + + export namespace Container { + export interface Data { + id: string; + + type: 'container'; + } + } + + export interface Terminal { + data: Terminal.Data; + } + + export namespace Terminal { + export interface Data { + id: string; + + type: 'terminal'; + } + } + } + + export interface Attributes { + /** + * A hash of all the changed attributes with the values being an array of the + * before and after. E.g. `{"pickup_lfd": [null, "2020-05-20"]}` + * + * The current attributes that can be alerted on are: + * + * - `available_for_pickup` + * - `pickup_lfd` + * - `fees_at_pod_terminal` + * - `holds_at_pod_terminal` + * - `pickup_appointment_at` + * - `pod_terminal` + */ + changeset: unknown; + + timestamp: string; + + data_source?: 'terminal'; + + /** + * IANA tz + */ + timezone?: string; + } + } +} + +export interface WebhookNotificationListResponse { + data?: Array; + + included?: Array< + WebhooksAPI.Webhook | TrackingRequestsAPI.TrackingRequest | ContainersAPI.TransportEvent | EstimatedEvent + >; + + links?: ShipmentsAPI.Links; + + meta?: ShipmentsAPI.Meta; +} + +export interface WebhookNotificationGetExamplesResponse { + data?: Array; + + included?: Array< + WebhooksAPI.Webhook | TrackingRequestsAPI.TrackingRequest | ContainersAPI.TransportEvent | EstimatedEvent + >; + + links?: ShipmentsAPI.Links; + + meta?: ShipmentsAPI.Meta; +} + +export interface WebhookNotificationRetrieveParams { + /** + * Comma delimited list of relations to include. + */ + include?: string; +} + +export interface WebhookNotificationListParams { + /** + * Comma delimited list of relations to include. + */ + include?: string; + + 'page[number]'?: number; + + 'page[size]'?: number; +} + +export interface WebhookNotificationGetExamplesParams { + /** + * The webhook notification event name you wish to see an example of + */ + event?: + | 'container.transport.vessel_arrived' + | 'container.transport.vessel_discharged' + | 'container.transport.vessel_loaded' + | 'container.transport.vessel_departed' + | 'container.transport.rail_departed' + | 'container.transport.rail_arrived' + | 'container.transport.rail_loaded' + | 'container.transport.rail_unloaded' + | 'container.transport.transshipment_arrived' + | 'container.transport.transshipment_discharged' + | 'container.transport.transshipment_loaded' + | 'container.transport.transshipment_departed' + | 'container.transport.feeder_arrived' + | 'container.transport.feeder_discharged' + | 'container.transport.feeder_loaded' + | 'container.transport.feeder_departed' + | 'container.transport.empty_out' + | 'container.transport.full_in' + | 'container.transport.full_out' + | 'container.transport.empty_in' + | 'container.transport.vessel_berthed' + | 'shipment.estimated.arrival' + | 'tracking_request.succeeded' + | 'tracking_request.failed' + | 'tracking_request.awaiting_manifest' + | 'tracking_request.tracking_stopped' + | 'container.created' + | 'container.updated' + | 'container.pod_terminal_changed' + | 'container.transport.arrived_at_inland_destination' + | 'container.transport.estimated.arrived_at_inland_destination' + | 'container.pickup_lfd.changed'; +} + +export declare namespace WebhookNotifications { + export { + type EstimatedEvent as EstimatedEvent, + type WebhookNotification as WebhookNotification, + type WebhookNotificationRetrieveResponse as WebhookNotificationRetrieveResponse, + type WebhookNotificationListResponse as WebhookNotificationListResponse, + type WebhookNotificationGetExamplesResponse as WebhookNotificationGetExamplesResponse, + type WebhookNotificationRetrieveParams as WebhookNotificationRetrieveParams, + type WebhookNotificationListParams as WebhookNotificationListParams, + type WebhookNotificationGetExamplesParams as WebhookNotificationGetExamplesParams, + }; +} diff --git a/src/resources/webhooks.ts b/src/resources/webhooks.ts new file mode 100644 index 00000000..db362933 --- /dev/null +++ b/src/resources/webhooks.ts @@ -0,0 +1,382 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import * as ShipmentsAPI from './shipments'; +import { APIPromise } from '../core/api-promise'; +import { buildHeaders } from '../internal/headers'; +import { RequestOptions } from '../internal/request-options'; +import { path } from '../internal/utils/path'; + +export class Webhooks extends APIResource { + /** + * You can configure a webhook via the API to be notified about events that happen + * in your Terminal49 account. These events can be realted to tracking_requests, + * shipments and containers. + * + * This is the recommended way tracking shipments and containers via the API. You + * should use this instead of polling our the API periodically. + * + * @example + * ```ts + * const webhook = await client.webhooks.create({ + * data: { + * attributes: { ... }, + * type: 'webhook', + * }, + * }); + * ``` + */ + create(body: WebhookCreateParams, options?: RequestOptions): APIPromise { + return this._client.post('/webhooks', { body, ...options }); + } + + /** + * Get the details of a single webhook + * + * @example + * ```ts + * const webhook = await client.webhooks.retrieve('id'); + * ``` + */ + retrieve(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/webhooks/${id}`, options); + } + + /** + * Update a single webhook + * + * @example + * ```ts + * const webhook = await client.webhooks.update('id', { + * data: { attributes: {}, type: 'webhook' }, + * }); + * ``` + */ + update(id: string, body: WebhookUpdateParams, options?: RequestOptions): APIPromise { + return this._client.patch(path`/webhooks/${id}`, { body, ...options }); + } + + /** + * Get a list of all the webhooks + * + * @example + * ```ts + * const webhooks = await client.webhooks.list(); + * ``` + */ + list( + query: WebhookListParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get('/webhooks', { query, ...options }); + } + + /** + * Delete a webhook + * + * @example + * ```ts + * await client.webhooks.delete('id'); + * ``` + */ + delete(id: string, options?: RequestOptions): APIPromise { + return this._client.delete(path`/webhooks/${id}`, { + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } + + /** + * Return the list of IPs used for sending webhook notifications. This can be + * useful for whitelisting the IPs on the firewall. + * + * @example + * ```ts + * const response = await client.webhooks.listIPs(); + * ``` + */ + listIPs(options?: RequestOptions): APIPromise { + return this._client.get('/webhooks/ips', options); + } +} + +export interface Webhook { + id: string; + + type: 'webhook'; + + attributes?: Webhook.Attributes; +} + +export namespace Webhook { + export interface Attributes { + /** + * Whether the webhook will be delivered when events are triggered + */ + active: boolean; + + /** + * The list of events to enabled for this endpoint + */ + events: Array< + | 'container.transport.vessel_arrived' + | 'container.transport.vessel_discharged' + | 'container.transport.vessel_loaded' + | 'container.transport.vessel_departed' + | 'container.transport.rail_departed' + | 'container.transport.rail_arrived' + | 'container.transport.rail_loaded' + | 'container.transport.rail_unloaded' + | 'container.transport.transshipment_arrived' + | 'container.transport.transshipment_discharged' + | 'container.transport.transshipment_loaded' + | 'container.transport.transshipment_departed' + | 'container.transport.feeder_arrived' + | 'container.transport.feeder_discharged' + | 'container.transport.feeder_loaded' + | 'container.transport.feeder_departed' + | 'container.transport.empty_out' + | 'container.transport.full_in' + | 'container.transport.full_out' + | 'container.transport.empty_in' + | 'container.transport.vessel_berthed' + | 'shipment.estimated.arrival' + | 'tracking_request.succeeded' + | 'tracking_request.failed' + | 'tracking_request.awaiting_manifest' + | 'tracking_request.tracking_stopped' + | 'container.created' + | 'container.updated' + | 'container.pod_terminal_changed' + | 'container.transport.arrived_at_inland_destination' + | 'container.transport.estimated.arrived_at_inland_destination' + | 'container.pickup_lfd.changed' + >; + + /** + * A random token that will sign all delivered webhooks + */ + secret: string; + + /** + * https end point + */ + url: string; + + headers?: Array | null; + } + + export namespace Attributes { + export interface Header { + name?: string; + + value?: string; + } + } +} + +export interface WebhookCreateResponse { + data?: Webhook; +} + +export interface WebhookRetrieveResponse { + data?: Webhook; +} + +export interface WebhookUpdateResponse { + data?: Webhook; +} + +export interface WebhookListResponse { + data?: Array; + + links?: ShipmentsAPI.Links; + + meta?: ShipmentsAPI.Meta; +} + +export interface WebhookListIPsResponse { + last_updated?: string; + + webhook_notification_ips?: Array; +} + +export interface WebhookCreateParams { + data: WebhookCreateParams.Data; +} + +export namespace WebhookCreateParams { + export interface Data { + attributes: Data.Attributes; + + type: 'webhook'; + } + + export namespace Data { + export interface Attributes { + active: boolean; + + /** + * The URL of the webhook endpoint. + */ + url: string; + + /** + * The list of events to enable for this endpoint. + */ + events?: Array< + | 'container.transport.vessel_arrived' + | 'container.transport.vessel_discharged' + | 'container.transport.vessel_loaded' + | 'container.transport.vessel_departed' + | 'container.transport.rail_departed' + | 'container.transport.rail_arrived' + | 'container.transport.rail_loaded' + | 'container.transport.rail_unloaded' + | 'container.transport.transshipment_arrived' + | 'container.transport.transshipment_discharged' + | 'container.transport.transshipment_loaded' + | 'container.transport.transshipment_departed' + | 'container.transport.feeder_arrived' + | 'container.transport.feeder_discharged' + | 'container.transport.feeder_loaded' + | 'container.transport.feeder_departed' + | 'container.transport.empty_out' + | 'container.transport.full_in' + | 'container.transport.full_out' + | 'container.transport.empty_in' + | 'container.transport.vessel_berthed' + | 'shipment.estimated.arrival' + | 'tracking_request.succeeded' + | 'tracking_request.failed' + | 'tracking_request.awaiting_manifest' + | 'tracking_request.tracking_stopped' + | 'container.created' + | 'container.updated' + | 'container.pod_terminal_changed' + | 'container.transport.arrived_at_inland_destination' + | 'container.transport.estimated.arrived_at_inland_destination' + | 'container.pickup_lfd.changed' + >; + + /** + * Optional custom headers to pass with each webhook invocation + */ + headers?: Array; + } + + export namespace Attributes { + export interface Header { + /** + * The name of the header. (Please note this will be auto-capitalized) + */ + name?: string; + + /** + * The value to pass for the header + */ + value?: string; + } + } + } +} + +export interface WebhookUpdateParams { + data: WebhookUpdateParams.Data; +} + +export namespace WebhookUpdateParams { + export interface Data { + attributes: Data.Attributes; + + type: 'webhook'; + } + + export namespace Data { + export interface Attributes { + active?: boolean; + + /** + * The list of events to enable for this endpoint. + */ + events?: Array< + | 'container.transport.vessel_arrived' + | 'container.transport.vessel_discharged' + | 'container.transport.vessel_loaded' + | 'container.transport.vessel_departed' + | 'container.transport.rail_departed' + | 'container.transport.rail_arrived' + | 'container.transport.rail_loaded' + | 'container.transport.rail_unloaded' + | 'container.transport.transshipment_arrived' + | 'container.transport.transshipment_discharged' + | 'container.transport.transshipment_loaded' + | 'container.transport.transshipment_departed' + | 'container.transport.feeder_arrived' + | 'container.transport.feeder_discharged' + | 'container.transport.feeder_loaded' + | 'container.transport.feeder_departed' + | 'container.transport.empty_out' + | 'container.transport.full_in' + | 'container.transport.full_out' + | 'container.transport.empty_in' + | 'container.transport.vessel_berthed' + | 'shipment.estimated.arrival' + | 'tracking_request.succeeded' + | 'tracking_request.failed' + | 'tracking_request.awaiting_manifest' + | 'tracking_request.tracking_stopped' + | 'container.created' + | 'container.updated' + | 'container.pod_terminal_changed' + | 'container.transport.arrived_at_inland_destination' + | 'container.transport.estimated.arrived_at_inland_destination' + | 'container.pickup_lfd.changed' + >; + + /** + * Optional custom headers to pass with each webhook invocation + */ + headers?: Array; + + /** + * The URL of the webhook endpoint. + */ + url?: string; + } + + export namespace Attributes { + export interface Header { + /** + * The name of the header. (Please not this will be auto-capitalized) + */ + name?: string; + + /** + * The value to pass for the header + */ + value?: string; + } + } + } +} + +export interface WebhookListParams { + 'page[number]'?: number; + + 'page[size]'?: number; +} + +export declare namespace Webhooks { + export { + type Webhook as Webhook, + type WebhookCreateResponse as WebhookCreateResponse, + type WebhookRetrieveResponse as WebhookRetrieveResponse, + type WebhookUpdateResponse as WebhookUpdateResponse, + type WebhookListResponse as WebhookListResponse, + type WebhookListIPsResponse as WebhookListIPsResponse, + type WebhookCreateParams as WebhookCreateParams, + type WebhookUpdateParams as WebhookUpdateParams, + type WebhookListParams as WebhookListParams, + }; +} diff --git a/src/uploads.ts b/src/uploads.ts new file mode 100644 index 00000000..b2ef6471 --- /dev/null +++ b/src/uploads.ts @@ -0,0 +1,2 @@ +/** @deprecated Import from ./core/uploads instead */ +export * from './core/uploads'; diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 00000000..b0bfd9e7 --- /dev/null +++ b/src/version.ts @@ -0,0 +1 @@ +export const VERSION = '0.1.0-alpha.1'; // x-release-please-version diff --git a/tests/api-resources/containers.test.ts b/tests/api-resources/containers.test.ts new file mode 100644 index 00000000..159ae4a2 --- /dev/null +++ b/tests/api-resources/containers.test.ts @@ -0,0 +1,112 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource containers', () => { + // Prism tests are disabled + test.skip('retrieve', async () => { + const responsePromise = client.containers.retrieve('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('retrieve: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.containers.retrieve('id', { include: 'include' }, { path: '/_stainless_unknown_path' }), + ).rejects.toThrow(Terminal49.NotFoundError); + }); + + // Prism tests are disabled + test.skip('update', async () => { + const responsePromise = client.containers.update(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('update: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.containers.update( + { data: { attributes: { ref_numbers: ['REF-12345'] } } }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Terminal49.NotFoundError); + }); + + // Prism tests are disabled + test.skip('list', async () => { + const responsePromise = client.containers.list(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('list: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.containers.list( + { include: 'include', 'page[number]': 0, 'page[size]': 0, terminal_checked_before: 0 }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Terminal49.NotFoundError); + }); + + // Prism tests are disabled + test.skip('getRawEvents', async () => { + const responsePromise = client.containers.getRawEvents('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('getTransportEvents', async () => { + const responsePromise = client.containers.getTransportEvents('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('getTransportEvents: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.containers.getTransportEvents( + 'id', + { include: 'include' }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Terminal49.NotFoundError); + }); +}); diff --git a/tests/api-resources/metro-areas.test.ts b/tests/api-resources/metro-areas.test.ts new file mode 100644 index 00000000..f6fa5bcb --- /dev/null +++ b/tests/api-resources/metro-areas.test.ts @@ -0,0 +1,22 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource metroAreas', () => { + // Prism tests are disabled + test.skip('retrieve', async () => { + const responsePromise = client.metroAreas.retrieve('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); +}); diff --git a/tests/api-resources/parties.test.ts b/tests/api-resources/parties.test.ts new file mode 100644 index 00000000..c6a09a69 --- /dev/null +++ b/tests/api-resources/parties.test.ts @@ -0,0 +1,89 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource parties', () => { + // Prism tests are disabled + test.skip('create', async () => { + const responsePromise = client.parties.create(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('create: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.parties.create( + { data: { attributes: { company_name: 'COMPANY NAME' } } }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Terminal49.NotFoundError); + }); + + // Prism tests are disabled + test.skip('retrieve', async () => { + const responsePromise = client.parties.retrieve('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('update', async () => { + const responsePromise = client.parties.update('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('update: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.parties.update( + 'id', + { data: { attributes: { company_name: 'COMPANY NAME' } } }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Terminal49.NotFoundError); + }); + + // Prism tests are disabled + test.skip('list', async () => { + const responsePromise = client.parties.list(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('list: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.parties.list({ 'page[number]': 0, 'page[size]': 0 }, { path: '/_stainless_unknown_path' }), + ).rejects.toThrow(Terminal49.NotFoundError); + }); +}); diff --git a/tests/api-resources/ports.test.ts b/tests/api-resources/ports.test.ts new file mode 100644 index 00000000..fe0fb3b6 --- /dev/null +++ b/tests/api-resources/ports.test.ts @@ -0,0 +1,22 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource ports', () => { + // Prism tests are disabled + test.skip('retrieve', async () => { + const responsePromise = client.ports.retrieve('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); +}); diff --git a/tests/api-resources/shipments.test.ts b/tests/api-resources/shipments.test.ts new file mode 100644 index 00000000..21fc59b4 --- /dev/null +++ b/tests/api-resources/shipments.test.ts @@ -0,0 +1,101 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource shipments', () => { + // Prism tests are disabled + test.skip('retrieve', async () => { + const responsePromise = client.shipments.retrieve('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('retrieve: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.shipments.retrieve('id', { include: 'include' }, { path: '/_stainless_unknown_path' }), + ).rejects.toThrow(Terminal49.NotFoundError); + }); + + // Prism tests are disabled + test.skip('update', async () => { + const responsePromise = client.shipments.update('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('update: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.shipments.update( + 'id', + { data: { attributes: { ref_numbers: ['REFNUMBER10'], shipment_tags: ['tag1, tag2'] } } }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Terminal49.NotFoundError); + }); + + // Prism tests are disabled + test.skip('list', async () => { + const responsePromise = client.shipments.list(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('list: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.shipments.list( + { include: 'include', number: 'number', 'page[number]': 0, 'page[size]': 0, q: 'q' }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Terminal49.NotFoundError); + }); + + // Prism tests are disabled + test.skip('resumeTracking', async () => { + const responsePromise = client.shipments.resumeTracking('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('stopTracking', async () => { + const responsePromise = client.shipments.stopTracking('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); +}); diff --git a/tests/api-resources/shipping-lines.test.ts b/tests/api-resources/shipping-lines.test.ts new file mode 100644 index 00000000..c132f4fd --- /dev/null +++ b/tests/api-resources/shipping-lines.test.ts @@ -0,0 +1,34 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource shippingLines', () => { + // Prism tests are disabled + test.skip('retrieve', async () => { + const responsePromise = client.shippingLines.retrieve('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('list', async () => { + const responsePromise = client.shippingLines.list(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); +}); diff --git a/tests/api-resources/terminals.test.ts b/tests/api-resources/terminals.test.ts new file mode 100644 index 00000000..fd7c6b45 --- /dev/null +++ b/tests/api-resources/terminals.test.ts @@ -0,0 +1,22 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource terminals', () => { + // Prism tests are disabled + test.skip('retrieve', async () => { + const responsePromise = client.terminals.retrieve('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); +}); diff --git a/tests/api-resources/tracking-requests.test.ts b/tests/api-resources/tracking-requests.test.ts new file mode 100644 index 00000000..641344f4 --- /dev/null +++ b/tests/api-resources/tracking-requests.test.ts @@ -0,0 +1,124 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource trackingRequests', () => { + // Prism tests are disabled + test.skip('create', async () => { + const responsePromise = client.trackingRequests.create(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('create: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.trackingRequests.create( + { + data: { + type: 'tracking_request', + attributes: { + request_number: 'MEDUFR030802', + request_type: 'bill_of_lading', + scac: 'MSCU', + ref_numbers: ['PO12345', 'HBL12345', 'CUSREF1234'], + shipment_tags: ['camembert'], + }, + relationships: { + customer: { data: { id: 'f7cb530a-9e60-412c-a5bc-205a2f34ba54', type: 'party' } }, + }, + }, + }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Terminal49.NotFoundError); + }); + + // Prism tests are disabled + test.skip('retrieve', async () => { + const responsePromise = client.trackingRequests.retrieve('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('retrieve: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.trackingRequests.retrieve('id', { include: 'include' }, { path: '/_stainless_unknown_path' }), + ).rejects.toThrow(Terminal49.NotFoundError); + }); + + // Prism tests are disabled + test.skip('update', async () => { + const responsePromise = client.trackingRequests.update('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('update: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.trackingRequests.update( + 'id', + { data: { attributes: { ref_number: 'REFNUMBER11' } } }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Terminal49.NotFoundError); + }); + + // Prism tests are disabled + test.skip('list', async () => { + const responsePromise = client.trackingRequests.list(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('list: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.trackingRequests.list( + { + 'filter[created_at][end]': '2020-04-28T22:59:15Z', + 'filter[created_at][start]': '2020-04-28T22:59:15Z', + 'filter[request_number]': 'filter[request_number]', + 'filter[scac]': 'MSCU', + 'filter[status]': 'created', + include: 'include', + 'page[number]': 0, + 'page[size]': 0, + q: 'q', + }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Terminal49.NotFoundError); + }); +}); diff --git a/tests/api-resources/vessels.test.ts b/tests/api-resources/vessels.test.ts new file mode 100644 index 00000000..7ea4518a --- /dev/null +++ b/tests/api-resources/vessels.test.ts @@ -0,0 +1,34 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource vessels', () => { + // Prism tests are disabled + test.skip('retrieveByID', async () => { + const responsePromise = client.vessels.retrieveByID('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('retrieveByImo', async () => { + const responsePromise = client.vessels.retrieveByImo('imo'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); +}); diff --git a/tests/api-resources/webhook-notifications.test.ts b/tests/api-resources/webhook-notifications.test.ts new file mode 100644 index 00000000..733e7204 --- /dev/null +++ b/tests/api-resources/webhook-notifications.test.ts @@ -0,0 +1,80 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource webhookNotifications', () => { + // Prism tests are disabled + test.skip('retrieve', async () => { + const responsePromise = client.webhookNotifications.retrieve('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('retrieve: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.webhookNotifications.retrieve( + 'id', + { include: 'include' }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Terminal49.NotFoundError); + }); + + // Prism tests are disabled + test.skip('list', async () => { + const responsePromise = client.webhookNotifications.list(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('list: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.webhookNotifications.list( + { include: 'include', 'page[number]': 0, 'page[size]': 0 }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Terminal49.NotFoundError); + }); + + // Prism tests are disabled + test.skip('getExamples', async () => { + const responsePromise = client.webhookNotifications.getExamples(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('getExamples: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.webhookNotifications.getExamples( + { event: 'container.transport.full_out' }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Terminal49.NotFoundError); + }); +}); diff --git a/tests/api-resources/webhooks.test.ts b/tests/api-resources/webhooks.test.ts new file mode 100644 index 00000000..3895602f --- /dev/null +++ b/tests/api-resources/webhooks.test.ts @@ -0,0 +1,155 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Terminal49 from 'terminal49'; + +const client = new Terminal49({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource webhooks', () => { + // Prism tests are disabled + test.skip('create: only required params', async () => { + const responsePromise = client.webhooks.create({ + data: { attributes: { active: true, url: 'https://webhook.site/' }, type: 'webhook' }, + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('create: required and optional params', async () => { + const response = await client.webhooks.create({ + data: { + attributes: { + active: true, + url: 'https://webhook.site/', + events: [ + 'container.transport.vessel_arrived', + 'container.transport.vessel_discharged', + 'container.transport.vessel_loaded', + 'container.transport.vessel_departed', + 'container.transport.rail_departed', + 'container.transport.rail_arrived', + 'container.transport.rail_loaded', + 'container.transport.rail_unloaded', + 'container.transport.transshipment_arrived', + 'container.transport.transshipment_discharged', + 'container.transport.transshipment_loaded', + 'container.transport.transshipment_departed', + 'container.transport.feeder_arrived', + 'container.transport.feeder_discharged', + 'container.transport.feeder_loaded', + 'container.transport.feeder_departed', + 'container.transport.empty_out', + 'container.transport.full_in', + 'container.transport.full_out', + 'container.transport.empty_in', + 'container.transport.vessel_berthed', + 'shipment.estimated.arrival', + 'tracking_request.succeeded', + 'tracking_request.failed', + 'tracking_request.awaiting_manifest', + 'tracking_request.tracking_stopped', + 'container.created', + 'container.updated', + 'container.pod_terminal_changed', + 'container.transport.arrived_at_inland_destination', + 'container.transport.estimated.arrived_at_inland_destination', + 'container.pickup_lfd.changed', + ], + headers: [{ name: 'name', value: 'value' }], + }, + type: 'webhook', + }, + }); + }); + + // Prism tests are disabled + test.skip('retrieve', async () => { + const responsePromise = client.webhooks.retrieve('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('update: only required params', async () => { + const responsePromise = client.webhooks.update('id', { data: { attributes: {}, type: 'webhook' } }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('update: required and optional params', async () => { + const response = await client.webhooks.update('id', { + data: { + attributes: { + active: true, + events: ['container.transport.vessel_arrived'], + headers: [{ name: 'name', value: 'value' }], + url: 'https://webhook.site/#!/39084fbb-d887-42e8-be08-b9183ad02362', + }, + type: 'webhook', + }, + }); + }); + + // Prism tests are disabled + test.skip('list', async () => { + const responsePromise = client.webhooks.list(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('list: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.webhooks.list({ 'page[number]': 0, 'page[size]': 0 }, { path: '/_stainless_unknown_path' }), + ).rejects.toThrow(Terminal49.NotFoundError); + }); + + // Prism tests are disabled + test.skip('delete', async () => { + const responsePromise = client.webhooks.delete('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('listIPs', async () => { + const responsePromise = client.webhooks.listIPs(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); +}); diff --git a/tests/base64.test.ts b/tests/base64.test.ts new file mode 100644 index 00000000..545cc891 --- /dev/null +++ b/tests/base64.test.ts @@ -0,0 +1,80 @@ +import { fromBase64, toBase64 } from 'terminal49/internal/utils/base64'; + +describe.each(['Buffer', 'atob'])('with %s', (mode) => { + let originalBuffer: BufferConstructor; + beforeAll(() => { + if (mode === 'atob') { + originalBuffer = globalThis.Buffer; + // @ts-expect-error Can't assign undefined to BufferConstructor + delete globalThis.Buffer; + } + }); + afterAll(() => { + if (mode === 'atob') { + globalThis.Buffer = originalBuffer; + } + }); + test('toBase64', () => { + const testCases = [ + { + input: 'hello world', + expected: 'aGVsbG8gd29ybGQ=', + }, + { + input: new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), + expected: 'aGVsbG8gd29ybGQ=', + }, + { + input: undefined, + expected: '', + }, + { + input: new Uint8Array([ + 229, 102, 215, 230, 65, 22, 46, 87, 243, 176, 99, 99, 31, 174, 8, 242, 83, 142, 169, 64, 122, 123, + 193, 71, + ]), + expected: '5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH', + }, + { + input: '✓', + expected: '4pyT', + }, + { + input: new Uint8Array([226, 156, 147]), + expected: '4pyT', + }, + ]; + + testCases.forEach(({ input, expected }) => { + expect(toBase64(input)).toBe(expected); + }); + }); + + test('fromBase64', () => { + const testCases = [ + { + input: 'aGVsbG8gd29ybGQ=', + expected: new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), + }, + { + input: '', + expected: new Uint8Array([]), + }, + { + input: '5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH', + expected: new Uint8Array([ + 229, 102, 215, 230, 65, 22, 46, 87, 243, 176, 99, 99, 31, 174, 8, 242, 83, 142, 169, 64, 122, 123, + 193, 71, + ]), + }, + { + input: '4pyT', + expected: new Uint8Array([226, 156, 147]), + }, + ]; + + testCases.forEach(({ input, expected }) => { + expect(fromBase64(input)).toEqual(expected); + }); + }); +}); diff --git a/tests/buildHeaders.test.ts b/tests/buildHeaders.test.ts new file mode 100644 index 00000000..47f6f7f6 --- /dev/null +++ b/tests/buildHeaders.test.ts @@ -0,0 +1,88 @@ +import { inspect } from 'node:util'; +import { buildHeaders, type HeadersLike, type NullableHeaders } from 'terminal49/internal/headers'; + +function inspectNullableHeaders(headers: NullableHeaders) { + return `NullableHeaders {${[ + ...[...headers.values.entries()].map(([name, value]) => ` ${inspect(name)}: ${inspect(value)}`), + ...[...headers.nulls].map((name) => ` ${inspect(name)}: null`), + ].join(', ')} }`; +} + +describe('buildHeaders', () => { + const cases: [HeadersLike[], string][] = [ + [[new Headers({ 'content-type': 'text/plain' })], `NullableHeaders { 'content-type': 'text/plain' }`], + [ + [ + { + 'content-type': 'text/plain', + }, + { + 'Content-Type': undefined, + }, + ], + `NullableHeaders { 'content-type': 'text/plain' }`, + ], + [ + [ + { + 'content-type': 'text/plain', + }, + { + 'Content-Type': null, + }, + ], + `NullableHeaders { 'content-type': null }`, + ], + [ + [ + { + cookie: 'name1=value1', + Cookie: 'name2=value2', + }, + ], + `NullableHeaders { 'cookie': 'name2=value2' }`, + ], + [ + [ + { + cookie: 'name1=value1', + Cookie: undefined, + }, + ], + `NullableHeaders { 'cookie': 'name1=value1' }`, + ], + [ + [ + { + cookie: ['name1=value1', 'name2=value2'], + }, + ], + `NullableHeaders { 'cookie': 'name1=value1; name2=value2' }`, + ], + [ + [ + { + 'x-foo': ['name1=value1', 'name2=value2'], + }, + ], + `NullableHeaders { 'x-foo': 'name1=value1, name2=value2' }`, + ], + [ + [ + [ + ['cookie', 'name1=value1'], + ['cookie', 'name2=value2'], + ['Cookie', 'name3=value3'], + ], + ], + `NullableHeaders { 'cookie': 'name1=value1; name2=value2; name3=value3' }`, + ], + [[undefined], `NullableHeaders { }`], + [[null], `NullableHeaders { }`], + ]; + for (const [input, expected] of cases) { + test(expected, () => { + expect(inspectNullableHeaders(buildHeaders(input))).toEqual(expected); + }); + } +}); diff --git a/tests/form.test.ts b/tests/form.test.ts new file mode 100644 index 00000000..4fe73c22 --- /dev/null +++ b/tests/form.test.ts @@ -0,0 +1,85 @@ +import { multipartFormRequestOptions, createForm } from 'terminal49/internal/uploads'; +import { toFile } from 'terminal49/core/uploads'; + +describe('form data validation', () => { + test('valid values do not error', async () => { + await multipartFormRequestOptions( + { + body: { + foo: 'foo', + string: 1, + bool: true, + file: await toFile(Buffer.from('some-content')), + blob: new Blob(['Some content'], { type: 'text/plain' }), + }, + }, + fetch, + ); + }); + + test('null', async () => { + await expect(() => + multipartFormRequestOptions( + { + body: { + null: null, + }, + }, + fetch, + ), + ).rejects.toThrow(TypeError); + }); + + test('undefined is stripped', async () => { + const form = await createForm( + { + foo: undefined, + bar: 'baz', + }, + fetch, + ); + expect(form.has('foo')).toBe(false); + expect(form.get('bar')).toBe('baz'); + }); + + test('nested undefined property is stripped', async () => { + const form = await createForm( + { + bar: { + baz: undefined, + }, + }, + fetch, + ); + expect(Array.from(form.entries())).toEqual([]); + + const form2 = await createForm( + { + bar: { + foo: 'string', + baz: undefined, + }, + }, + fetch, + ); + expect(Array.from(form2.entries())).toEqual([['bar[foo]', 'string']]); + }); + + test('nested undefined array item is stripped', async () => { + const form = await createForm( + { + bar: [undefined, undefined], + }, + fetch, + ); + expect(Array.from(form.entries())).toEqual([]); + + const form2 = await createForm( + { + bar: [undefined, 'foo'], + }, + fetch, + ); + expect(Array.from(form2.entries())).toEqual([['bar[]', 'foo']]); + }); +}); diff --git a/tests/index.test.ts b/tests/index.test.ts new file mode 100644 index 00000000..e59496a9 --- /dev/null +++ b/tests/index.test.ts @@ -0,0 +1,734 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIPromise } from 'terminal49/core/api-promise'; + +import util from 'node:util'; +import Terminal49 from 'terminal49'; +import { APIUserAbortError } from 'terminal49'; +const defaultFetch = fetch; + +describe('instantiate client', () => { + const env = process.env; + + beforeEach(() => { + jest.resetModules(); + process.env = { ...env }; + }); + + afterEach(() => { + process.env = env; + }); + + describe('defaultHeaders', () => { + const client = new Terminal49({ + baseURL: 'http://localhost:5000/', + defaultHeaders: { 'X-My-Default-Header': '2' }, + apiKey: 'My API Key', + }); + + test('they are used in the request', async () => { + const { req } = await client.buildRequest({ path: '/foo', method: 'post' }); + expect(req.headers.get('x-my-default-header')).toEqual('2'); + }); + + test('can ignore `undefined` and leave the default', async () => { + const { req } = await client.buildRequest({ + path: '/foo', + method: 'post', + headers: { 'X-My-Default-Header': undefined }, + }); + expect(req.headers.get('x-my-default-header')).toEqual('2'); + }); + + test('can be removed with `null`', async () => { + const { req } = await client.buildRequest({ + path: '/foo', + method: 'post', + headers: { 'X-My-Default-Header': null }, + }); + expect(req.headers.has('x-my-default-header')).toBe(false); + }); + }); + describe('logging', () => { + const env = process.env; + + beforeEach(() => { + process.env = { ...env }; + process.env['TERMINAL49_LOG'] = undefined; + }); + + afterEach(() => { + process.env = env; + }); + + const forceAPIResponseForClient = async (client: Terminal49) => { + await new APIPromise( + client, + Promise.resolve({ + response: new Response(), + controller: new AbortController(), + requestLogID: 'log_000000', + retryOfRequestLogID: undefined, + startTime: Date.now(), + options: { + method: 'get', + path: '/', + }, + }), + ); + }; + + test('debug logs when log level is debug', async () => { + const debugMock = jest.fn(); + const logger = { + debug: debugMock, + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + + const client = new Terminal49({ logger: logger, logLevel: 'debug', apiKey: 'My API Key' }); + + await forceAPIResponseForClient(client); + expect(debugMock).toHaveBeenCalled(); + }); + + test('default logLevel is warn', async () => { + const client = new Terminal49({ apiKey: 'My API Key' }); + expect(client.logLevel).toBe('warn'); + }); + + test('debug logs are skipped when log level is info', async () => { + const debugMock = jest.fn(); + const logger = { + debug: debugMock, + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + + const client = new Terminal49({ logger: logger, logLevel: 'info', apiKey: 'My API Key' }); + + await forceAPIResponseForClient(client); + expect(debugMock).not.toHaveBeenCalled(); + }); + + test('debug logs happen with debug env var', async () => { + const debugMock = jest.fn(); + const logger = { + debug: debugMock, + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + + process.env['TERMINAL49_LOG'] = 'debug'; + const client = new Terminal49({ logger: logger, apiKey: 'My API Key' }); + expect(client.logLevel).toBe('debug'); + + await forceAPIResponseForClient(client); + expect(debugMock).toHaveBeenCalled(); + }); + + test('warn when env var level is invalid', async () => { + const warnMock = jest.fn(); + const logger = { + debug: jest.fn(), + info: jest.fn(), + warn: warnMock, + error: jest.fn(), + }; + + process.env['TERMINAL49_LOG'] = 'not a log level'; + const client = new Terminal49({ logger: logger, apiKey: 'My API Key' }); + expect(client.logLevel).toBe('warn'); + expect(warnMock).toHaveBeenCalledWith( + 'process.env[\'TERMINAL49_LOG\'] was set to "not a log level", expected one of ["off","error","warn","info","debug"]', + ); + }); + + test('client log level overrides env var', async () => { + const debugMock = jest.fn(); + const logger = { + debug: debugMock, + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + + process.env['TERMINAL49_LOG'] = 'debug'; + const client = new Terminal49({ logger: logger, logLevel: 'off', apiKey: 'My API Key' }); + + await forceAPIResponseForClient(client); + expect(debugMock).not.toHaveBeenCalled(); + }); + + test('no warning logged for invalid env var level + valid client level', async () => { + const warnMock = jest.fn(); + const logger = { + debug: jest.fn(), + info: jest.fn(), + warn: warnMock, + error: jest.fn(), + }; + + process.env['TERMINAL49_LOG'] = 'not a log level'; + const client = new Terminal49({ logger: logger, logLevel: 'debug', apiKey: 'My API Key' }); + expect(client.logLevel).toBe('debug'); + expect(warnMock).not.toHaveBeenCalled(); + }); + }); + + describe('defaultQuery', () => { + test('with null query params given', () => { + const client = new Terminal49({ + baseURL: 'http://localhost:5000/', + defaultQuery: { apiVersion: 'foo' }, + apiKey: 'My API Key', + }); + expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo'); + }); + + test('multiple default query params', () => { + const client = new Terminal49({ + baseURL: 'http://localhost:5000/', + defaultQuery: { apiVersion: 'foo', hello: 'world' }, + apiKey: 'My API Key', + }); + expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo&hello=world'); + }); + + test('overriding with `undefined`', () => { + const client = new Terminal49({ + baseURL: 'http://localhost:5000/', + defaultQuery: { hello: 'world' }, + apiKey: 'My API Key', + }); + expect(client.buildURL('/foo', { hello: undefined })).toEqual('http://localhost:5000/foo'); + }); + }); + + test('custom fetch', async () => { + const client = new Terminal49({ + baseURL: 'http://localhost:5000/', + apiKey: 'My API Key', + fetch: (url) => { + return Promise.resolve( + new Response(JSON.stringify({ url, custom: true }), { + headers: { 'Content-Type': 'application/json' }, + }), + ); + }, + }); + + const response = await client.get('/foo'); + expect(response).toEqual({ url: 'http://localhost:5000/foo', custom: true }); + }); + + test('explicit global fetch', async () => { + // make sure the global fetch type is assignable to our Fetch type + const client = new Terminal49({ + baseURL: 'http://localhost:5000/', + apiKey: 'My API Key', + fetch: defaultFetch, + }); + }); + + test('custom signal', async () => { + const client = new Terminal49({ + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', + apiKey: 'My API Key', + fetch: (...args) => { + return new Promise((resolve, reject) => + setTimeout( + () => + defaultFetch(...args) + .then(resolve) + .catch(reject), + 300, + ), + ); + }, + }); + + const controller = new AbortController(); + setTimeout(() => controller.abort(), 200); + + const spy = jest.spyOn(client, 'request'); + + await expect(client.get('/foo', { signal: controller.signal })).rejects.toThrowError(APIUserAbortError); + expect(spy).toHaveBeenCalledTimes(1); + }); + + test('normalized method', async () => { + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { + capturedRequest = init; + return new Response(JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } }); + }; + + const client = new Terminal49({ + baseURL: 'http://localhost:5000/', + apiKey: 'My API Key', + fetch: testFetch, + }); + + await client.patch('/foo'); + expect(capturedRequest?.method).toEqual('PATCH'); + }); + + describe('baseUrl', () => { + test('trailing slash', () => { + const client = new Terminal49({ baseURL: 'http://localhost:5000/custom/path/', apiKey: 'My API Key' }); + expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); + }); + + test('no trailing slash', () => { + const client = new Terminal49({ baseURL: 'http://localhost:5000/custom/path', apiKey: 'My API Key' }); + expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); + }); + + afterEach(() => { + process.env['TERMINAL49_BASE_URL'] = undefined; + }); + + test('explicit option', () => { + const client = new Terminal49({ baseURL: 'https://example.com', apiKey: 'My API Key' }); + expect(client.baseURL).toEqual('https://example.com'); + }); + + test('env variable', () => { + process.env['TERMINAL49_BASE_URL'] = 'https://example.com/from_env'; + const client = new Terminal49({ apiKey: 'My API Key' }); + expect(client.baseURL).toEqual('https://example.com/from_env'); + }); + + test('empty env variable', () => { + process.env['TERMINAL49_BASE_URL'] = ''; // empty + const client = new Terminal49({ apiKey: 'My API Key' }); + expect(client.baseURL).toEqual('https://api.terminal49.com/v2'); + }); + + test('blank env variable', () => { + process.env['TERMINAL49_BASE_URL'] = ' '; // blank + const client = new Terminal49({ apiKey: 'My API Key' }); + expect(client.baseURL).toEqual('https://api.terminal49.com/v2'); + }); + + test('in request options', () => { + const client = new Terminal49({ apiKey: 'My API Key' }); + expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( + 'http://localhost:5000/option/foo', + ); + }); + + test('in request options overridden by client options', () => { + const client = new Terminal49({ apiKey: 'My API Key', baseURL: 'http://localhost:5000/client' }); + expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( + 'http://localhost:5000/client/foo', + ); + }); + + test('in request options overridden by env variable', () => { + process.env['TERMINAL49_BASE_URL'] = 'http://localhost:5000/env'; + const client = new Terminal49({ apiKey: 'My API Key' }); + expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( + 'http://localhost:5000/env/foo', + ); + }); + }); + + test('maxRetries option is correctly set', () => { + const client = new Terminal49({ maxRetries: 4, apiKey: 'My API Key' }); + expect(client.maxRetries).toEqual(4); + + // default + const client2 = new Terminal49({ apiKey: 'My API Key' }); + expect(client2.maxRetries).toEqual(2); + }); + + describe('withOptions', () => { + test('creates a new client with overridden options', async () => { + const client = new Terminal49({ + baseURL: 'http://localhost:5000/', + maxRetries: 3, + apiKey: 'My API Key', + }); + + const newClient = client.withOptions({ + maxRetries: 5, + baseURL: 'http://localhost:5001/', + }); + + // Verify the new client has updated options + expect(newClient.maxRetries).toEqual(5); + expect(newClient.baseURL).toEqual('http://localhost:5001/'); + + // Verify the original client is unchanged + expect(client.maxRetries).toEqual(3); + expect(client.baseURL).toEqual('http://localhost:5000/'); + + // Verify it's a different instance + expect(newClient).not.toBe(client); + expect(newClient.constructor).toBe(client.constructor); + }); + + test('inherits options from the parent client', async () => { + const client = new Terminal49({ + baseURL: 'http://localhost:5000/', + defaultHeaders: { 'X-Test-Header': 'test-value' }, + defaultQuery: { 'test-param': 'test-value' }, + apiKey: 'My API Key', + }); + + const newClient = client.withOptions({ + baseURL: 'http://localhost:5001/', + }); + + // Test inherited options remain the same + expect(newClient.buildURL('/foo', null)).toEqual('http://localhost:5001/foo?test-param=test-value'); + + const { req } = await newClient.buildRequest({ path: '/foo', method: 'get' }); + expect(req.headers.get('x-test-header')).toEqual('test-value'); + }); + + test('respects runtime property changes when creating new client', () => { + const client = new Terminal49({ + baseURL: 'http://localhost:5000/', + timeout: 1000, + apiKey: 'My API Key', + }); + + // Modify the client properties directly after creation + client.baseURL = 'http://localhost:6000/'; + client.timeout = 2000; + + // Create a new client with withOptions + const newClient = client.withOptions({ + maxRetries: 10, + }); + + // Verify the new client uses the updated properties, not the original ones + expect(newClient.baseURL).toEqual('http://localhost:6000/'); + expect(newClient.timeout).toEqual(2000); + expect(newClient.maxRetries).toEqual(10); + + // Original client should still have its modified properties + expect(client.baseURL).toEqual('http://localhost:6000/'); + expect(client.timeout).toEqual(2000); + expect(client.maxRetries).not.toEqual(10); + + // Verify URL building uses the updated baseURL + expect(newClient.buildURL('/bar', null)).toEqual('http://localhost:6000/bar'); + }); + }); + + test('with environment variable arguments', () => { + // set options via env var + process.env['TERMINAL49_API_KEY'] = 'My API Key'; + const client = new Terminal49(); + expect(client.apiKey).toBe('My API Key'); + }); + + test('with overridden environment variable arguments', () => { + // set options via env var + process.env['TERMINAL49_API_KEY'] = 'another My API Key'; + const client = new Terminal49({ apiKey: 'My API Key' }); + expect(client.apiKey).toBe('My API Key'); + }); +}); + +describe('request building', () => { + const client = new Terminal49({ apiKey: 'My API Key' }); + + describe('custom headers', () => { + test('handles undefined', async () => { + const { req } = await client.buildRequest({ + path: '/foo', + method: 'post', + body: { value: 'hello' }, + headers: { 'X-Foo': 'baz', 'x-foo': 'bar', 'x-Foo': undefined, 'x-baz': 'bam', 'X-Baz': null }, + }); + expect(req.headers.get('x-foo')).toEqual('bar'); + expect(req.headers.get('x-Foo')).toEqual('bar'); + expect(req.headers.get('X-Foo')).toEqual('bar'); + expect(req.headers.get('x-baz')).toEqual(null); + }); + }); +}); + +describe('default encoder', () => { + const client = new Terminal49({ apiKey: 'My API Key' }); + + class Serializable { + toJSON() { + return { $type: 'Serializable' }; + } + } + class Collection { + #things: T[]; + constructor(things: T[]) { + this.#things = Array.from(things); + } + toJSON() { + return Array.from(this.#things); + } + [Symbol.iterator]() { + return this.#things[Symbol.iterator]; + } + } + for (const jsonValue of [{}, [], { __proto__: null }, new Serializable(), new Collection(['item'])]) { + test(`serializes ${util.inspect(jsonValue)} as json`, async () => { + const { req } = await client.buildRequest({ + path: '/foo', + method: 'post', + body: jsonValue, + }); + expect(req.headers).toBeInstanceOf(Headers); + expect(req.headers.get('content-type')).toEqual('application/json'); + expect(req.body).toBe(JSON.stringify(jsonValue)); + }); + } + + const encoder = new TextEncoder(); + const asyncIterable = (async function* () { + yield encoder.encode('a\n'); + yield encoder.encode('b\n'); + yield encoder.encode('c\n'); + })(); + for (const streamValue of [ + [encoder.encode('a\nb\nc\n')][Symbol.iterator](), + new Response('a\nb\nc\n').body, + asyncIterable, + ]) { + test(`converts ${util.inspect(streamValue)} to ReadableStream`, async () => { + const { req } = await client.buildRequest({ + path: '/foo', + method: 'post', + body: streamValue, + }); + expect(req.headers).toBeInstanceOf(Headers); + expect(req.headers.get('content-type')).toEqual(null); + expect(req.body).toBeInstanceOf(ReadableStream); + expect(await new Response(req.body).text()).toBe('a\nb\nc\n'); + }); + } + + test(`can set content-type for ReadableStream`, async () => { + const { req } = await client.buildRequest({ + path: '/foo', + method: 'post', + body: new Response('a\nb\nc\n').body, + headers: { 'Content-Type': 'text/plain' }, + }); + expect(req.headers).toBeInstanceOf(Headers); + expect(req.headers.get('content-type')).toEqual('text/plain'); + expect(req.body).toBeInstanceOf(ReadableStream); + expect(await new Response(req.body).text()).toBe('a\nb\nc\n'); + }); +}); + +describe('retries', () => { + test('retry on timeout', async () => { + let count = 0; + const testFetch = async ( + url: string | URL | Request, + { signal }: RequestInit = {}, + ): Promise => { + if (count++ === 0) { + return new Promise( + (resolve, reject) => signal?.addEventListener('abort', () => reject(new Error('timed out'))), + ); + } + return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); + }; + + const client = new Terminal49({ apiKey: 'My API Key', timeout: 10, fetch: testFetch }); + + expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); + expect(count).toEqual(2); + expect( + await client + .request({ path: '/foo', method: 'get' }) + .asResponse() + .then((r) => r.text()), + ).toEqual(JSON.stringify({ a: 1 })); + expect(count).toEqual(3); + }); + + test('retry count header', async () => { + let count = 0; + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { + count++; + if (count <= 2) { + return new Response(undefined, { + status: 429, + headers: { + 'Retry-After': '0.1', + }, + }); + } + capturedRequest = init; + return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); + }; + + const client = new Terminal49({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 }); + + expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); + + expect((capturedRequest!.headers as Headers).get('x-stainless-retry-count')).toEqual('2'); + expect(count).toEqual(3); + }); + + test('omit retry count header', async () => { + let count = 0; + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { + count++; + if (count <= 2) { + return new Response(undefined, { + status: 429, + headers: { + 'Retry-After': '0.1', + }, + }); + } + capturedRequest = init; + return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); + }; + const client = new Terminal49({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 }); + + expect( + await client.request({ + path: '/foo', + method: 'get', + headers: { 'X-Stainless-Retry-Count': null }, + }), + ).toEqual({ a: 1 }); + + expect((capturedRequest!.headers as Headers).has('x-stainless-retry-count')).toBe(false); + }); + + test('omit retry count header by default', async () => { + let count = 0; + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { + count++; + if (count <= 2) { + return new Response(undefined, { + status: 429, + headers: { + 'Retry-After': '0.1', + }, + }); + } + capturedRequest = init; + return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); + }; + const client = new Terminal49({ + apiKey: 'My API Key', + fetch: testFetch, + maxRetries: 4, + defaultHeaders: { 'X-Stainless-Retry-Count': null }, + }); + + expect( + await client.request({ + path: '/foo', + method: 'get', + }), + ).toEqual({ a: 1 }); + + expect(capturedRequest!.headers as Headers).not.toHaveProperty('x-stainless-retry-count'); + }); + + test('overwrite retry count header', async () => { + let count = 0; + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { + count++; + if (count <= 2) { + return new Response(undefined, { + status: 429, + headers: { + 'Retry-After': '0.1', + }, + }); + } + capturedRequest = init; + return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); + }; + const client = new Terminal49({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 }); + + expect( + await client.request({ + path: '/foo', + method: 'get', + headers: { 'X-Stainless-Retry-Count': '42' }, + }), + ).toEqual({ a: 1 }); + + expect((capturedRequest!.headers as Headers).get('x-stainless-retry-count')).toEqual('42'); + }); + + test('retry on 429 with retry-after', async () => { + let count = 0; + const testFetch = async ( + url: string | URL | Request, + { signal }: RequestInit = {}, + ): Promise => { + if (count++ === 0) { + return new Response(undefined, { + status: 429, + headers: { + 'Retry-After': '0.1', + }, + }); + } + return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); + }; + + const client = new Terminal49({ apiKey: 'My API Key', fetch: testFetch }); + + expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); + expect(count).toEqual(2); + expect( + await client + .request({ path: '/foo', method: 'get' }) + .asResponse() + .then((r) => r.text()), + ).toEqual(JSON.stringify({ a: 1 })); + expect(count).toEqual(3); + }); + + test('retry on 429 with retry-after-ms', async () => { + let count = 0; + const testFetch = async ( + url: string | URL | Request, + { signal }: RequestInit = {}, + ): Promise => { + if (count++ === 0) { + return new Response(undefined, { + status: 429, + headers: { + 'Retry-After-Ms': '10', + }, + }); + } + return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); + }; + + const client = new Terminal49({ apiKey: 'My API Key', fetch: testFetch }); + + expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); + expect(count).toEqual(2); + expect( + await client + .request({ path: '/foo', method: 'get' }) + .asResponse() + .then((r) => r.text()), + ).toEqual(JSON.stringify({ a: 1 })); + expect(count).toEqual(3); + }); +}); diff --git a/tests/path.test.ts b/tests/path.test.ts new file mode 100644 index 00000000..2c6f3c83 --- /dev/null +++ b/tests/path.test.ts @@ -0,0 +1,462 @@ +import { createPathTagFunction, encodeURIPath } from 'terminal49/internal/utils/path'; +import { inspect } from 'node:util'; +import { runInNewContext } from 'node:vm'; + +describe('path template tag function', () => { + test('validates input', () => { + const testParams = ['', '.', '..', 'x', '%2e', '%2E', '%2e%2e', '%2E%2e', '%2e%2E', '%2E%2E']; + const testCases = [ + ['/path_params/', '/a'], + ['/path_params/', '/'], + ['/path_params/', ''], + ['', '/a'], + ['', '/'], + ['', ''], + ['a'], + [''], + ['/path_params/', ':initiate'], + ['/path_params/', '.json'], + ['/path_params/', '?beta=true'], + ['/path_params/', '.?beta=true'], + ['/path_params/', '/', '/download'], + ['/path_params/', '-', '/download'], + ['/path_params/', '', '/download'], + ['/path_params/', '.', '/download'], + ['/path_params/', '..', '/download'], + ['/plain/path'], + ]; + + function paramPermutations(len: number): string[][] { + if (len === 0) return []; + if (len === 1) return testParams.map((e) => [e]); + const rest = paramPermutations(len - 1); + return testParams.flatMap((e) => rest.map((r) => [e, ...r])); + } + + // We need to test how %2E is handled, so we use a custom encoder that does no escaping. + const rawPath = createPathTagFunction((s) => s); + + const emptyObject = {}; + const mathObject = Math; + const numberObject = new Number(); + const stringObject = new String(); + const basicClass = new (class {})(); + const classWithToString = new (class { + toString() { + return 'ok'; + } + })(); + + // Invalid values + expect(() => rawPath`/a/${null}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Null is not a valid path parameter\n' + + '/a/null/b\n' + + ' ^^^^', + ); + expect(() => rawPath`/a/${undefined}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Undefined is not a valid path parameter\n' + + '/a/undefined/b\n' + + ' ^^^^^^^^^', + ); + expect(() => rawPath`/a/${emptyObject}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/a/[object Object]/b\n' + + ' ^^^^^^^^^^^^^^^', + ); + expect(() => rawPath`?${mathObject}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Math is not a valid path parameter\n' + + '?[object Math]\n' + + ' ^^^^^^^^^^^^^', + ); + expect(() => rawPath`/${basicClass}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/[object Object]\n' + + ' ^^^^^^^^^^^^^^', + ); + expect(() => rawPath`/../${''}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + '/../\n' + + ' ^^', + ); + expect(() => rawPath`/../${{}}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + 'Value of type Object is not a valid path parameter\n' + + '/../[object Object]\n' + + ' ^^ ^^^^^^^^^^^^^^', + ); + + // Valid values + expect(rawPath`/${0}`).toBe('/0'); + expect(rawPath`/${''}`).toBe('/'); + expect(rawPath`/${numberObject}`).toBe('/0'); + expect(rawPath`${stringObject}/`).toBe('/'); + expect(rawPath`/${classWithToString}`).toBe('/ok'); + + // We need to check what happens with cross-realm values, which we might get from + // Jest or other frames in a browser. + + const newRealm = runInNewContext('globalThis'); + expect(newRealm.Object).not.toBe(Object); + + const crossRealmObject = newRealm.Object(); + const crossRealmMathObject = newRealm.Math; + const crossRealmNumber = new newRealm.Number(); + const crossRealmString = new newRealm.String(); + const crossRealmClass = new (class extends newRealm.Object {})(); + const crossRealmClassWithToString = new (class extends newRealm.Object { + toString() { + return 'ok'; + } + })(); + + // Invalid cross-realm values + expect(() => rawPath`/a/${crossRealmObject}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/a/[object Object]/b\n' + + ' ^^^^^^^^^^^^^^^', + ); + expect(() => rawPath`?${crossRealmMathObject}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Math is not a valid path parameter\n' + + '?[object Math]\n' + + ' ^^^^^^^^^^^^^', + ); + expect(() => rawPath`/${crossRealmClass}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/[object Object]\n' + + ' ^^^^^^^^^^^^^^^', + ); + + // Valid cross-realm values + expect(rawPath`/${crossRealmNumber}`).toBe('/0'); + expect(rawPath`${crossRealmString}/`).toBe('/'); + expect(rawPath`/${crossRealmClassWithToString}`).toBe('/ok'); + + const results: { + [pathParts: string]: { + [params: string]: { valid: boolean; result?: string; error?: string }; + }; + } = {}; + + for (const pathParts of testCases) { + const pathResults: Record = {}; + results[JSON.stringify(pathParts)] = pathResults; + for (const params of paramPermutations(pathParts.length - 1)) { + const stringRaw = String.raw({ raw: pathParts }, ...params); + const plainString = String.raw( + { raw: pathParts.map((e) => e.replace(/\./g, 'x')) }, + ...params.map((e) => 'X'.repeat(e.length)), + ); + const normalizedStringRaw = new URL(stringRaw, 'https://example.com').href; + const normalizedPlainString = new URL(plainString, 'https://example.com').href; + const pathResultsKey = JSON.stringify(params); + try { + const result = rawPath(pathParts, ...params); + expect(result).toBe(stringRaw); + // there are no special segments, so the length of the normalized path is + // equal to the length of the normalized plain path. + expect(normalizedStringRaw.length).toBe(normalizedPlainString.length); + pathResults[pathResultsKey] = { + valid: true, + result, + }; + } catch (e) { + const error = String(e); + expect(error).toMatch(/Path parameters result in path with invalid segment/); + // there are special segments, so the length of the normalized path is + // different than the length of the normalized plain path. + expect(normalizedStringRaw.length).not.toBe(normalizedPlainString.length); + pathResults[pathResultsKey] = { + valid: false, + error, + }; + } + } + } + + expect(results).toMatchObject({ + '["/path_params/","/a"]': { + '["x"]': { valid: true, result: '/path_params/x/a' }, + '[""]': { valid: true, result: '/path_params//a' }, + '["%2E%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E%2e/a\n' + + ' ^^^^^^', + }, + '["%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E/a\n' + + ' ^^^', + }, + }, + '["/path_params/","/"]': { + '["x"]': { valid: true, result: '/path_params/x/' }, + '[""]': { valid: true, result: '/path_params//' }, + '["%2e%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2e%2E/\n' + + ' ^^^^^^', + }, + '["%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e" can\'t be safely passed as a path parameter\n' + + '/path_params/%2e/\n' + + ' ^^^', + }, + }, + '["/path_params/",""]': { + '[""]': { valid: true, result: '/path_params/' }, + '["x"]': { valid: true, result: '/path_params/x' }, + '["%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E\n' + + ' ^^^', + }, + '["%2E%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E%2e\n' + + ' ^^^^^^', + }, + }, + '["","/a"]': { + '[""]': { valid: true, result: '/a' }, + '["x"]': { valid: true, result: 'x/a' }, + '["%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n%2E/a\n^^^', + }, + '["%2e%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + + '%2e%2E/a\n' + + '^^^^^^', + }, + }, + '["","/"]': { + '["x"]': { valid: true, result: 'x/' }, + '[""]': { valid: true, result: '/' }, + '["%2E%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + + '%2E%2e/\n' + + '^^^^^^', + }, + '["."]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + './\n^', + }, + }, + '["",""]': { + '[""]': { valid: true, result: '' }, + '["x"]': { valid: true, result: 'x' }, + '[".."]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + '..\n^^', + }, + '["."]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + '.\n^', + }, + }, + '["a"]': {}, + '[""]': {}, + '["/path_params/",":initiate"]': { + '[""]': { valid: true, result: '/path_params/:initiate' }, + '["."]': { valid: true, result: '/path_params/.:initiate' }, + }, + '["/path_params/",".json"]': { + '["x"]': { valid: true, result: '/path_params/x.json' }, + '["."]': { valid: true, result: '/path_params/..json' }, + }, + '["/path_params/","?beta=true"]': { + '["x"]': { valid: true, result: '/path_params/x?beta=true' }, + '[""]': { valid: true, result: '/path_params/?beta=true' }, + '["%2E%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E%2E?beta=true\n' + + ' ^^^^^^', + }, + '["%2e%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2e%2E?beta=true\n' + + ' ^^^^^^', + }, + }, + '["/path_params/",".?beta=true"]': { + '[".."]': { valid: true, result: '/path_params/...?beta=true' }, + '["x"]': { valid: true, result: '/path_params/x.?beta=true' }, + '[""]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + '/path_params/.?beta=true\n' + + ' ^', + }, + '["%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e." can\'t be safely passed as a path parameter\n' + + '/path_params/%2e.?beta=true\n' + + ' ^^^^', + }, + }, + '["/path_params/","/","/download"]': { + '["",""]': { valid: true, result: '/path_params///download' }, + '["","x"]': { valid: true, result: '/path_params//x/download' }, + '[".","%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + 'Value "%2e" can\'t be safely passed as a path parameter\n' + + '/path_params/./%2e/download\n' + + ' ^ ^^^', + }, + '["%2E%2e","%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + + 'Value "%2e" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E%2e/%2e/download\n' + + ' ^^^^^^ ^^^', + }, + }, + '["/path_params/","-","/download"]': { + '["","%2e"]': { valid: true, result: '/path_params/-%2e/download' }, + '["%2E",".."]': { valid: true, result: '/path_params/%2E-../download' }, + }, + '["/path_params/","","/download"]': { + '["%2E%2e","%2e%2E"]': { valid: true, result: '/path_params/%2E%2e%2e%2E/download' }, + '["%2E",".."]': { valid: true, result: '/path_params/%2E../download' }, + '["","%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E/download\n' + + ' ^^^', + }, + '["%2E","."]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E." can\'t be safely passed as a path parameter\n' + + '/path_params/%2E./download\n' + + ' ^^^^', + }, + }, + '["/path_params/",".","/download"]': { + '["%2e%2e",""]': { valid: true, result: '/path_params/%2e%2e./download' }, + '["","%2e%2e"]': { valid: true, result: '/path_params/.%2e%2e/download' }, + '["",""]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + '/path_params/./download\n' + + ' ^', + }, + '["","."]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + '/path_params/../download\n' + + ' ^^', + }, + }, + '["/path_params/","..","/download"]': { + '["","%2E"]': { valid: true, result: '/path_params/..%2E/download' }, + '["","x"]': { valid: true, result: '/path_params/..x/download' }, + '["",""]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + '/path_params/../download\n' + + ' ^^', + }, + }, + }); + }); +}); + +describe('encodeURIPath', () => { + const testCases: string[] = [ + '', + // Every ASCII character + ...Array.from({ length: 0x7f }, (_, i) => String.fromCharCode(i)), + // Unicode BMP codepoint + 'å', + // Unicode supplementary codepoint + '😃', + ]; + + for (const param of testCases) { + test('properly encodes ' + inspect(param), () => { + const encoded = encodeURIPath(param); + const naiveEncoded = encodeURIComponent(param); + // we should never encode more characters than encodeURIComponent + expect(naiveEncoded.length).toBeGreaterThanOrEqual(encoded.length); + expect(decodeURIComponent(encoded)).toBe(param); + }); + } + + test("leaves ':' intact", () => { + expect(encodeURIPath(':')).toBe(':'); + }); + + test("leaves '@' intact", () => { + expect(encodeURIPath('@')).toBe('@'); + }); +}); diff --git a/tests/stringifyQuery.test.ts b/tests/stringifyQuery.test.ts new file mode 100644 index 00000000..efdc89e8 --- /dev/null +++ b/tests/stringifyQuery.test.ts @@ -0,0 +1,29 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Terminal49 } from 'terminal49'; + +const { stringifyQuery } = Terminal49.prototype as any; + +describe(stringifyQuery, () => { + for (const [input, expected] of [ + [{ a: '1', b: 2, c: true }, 'a=1&b=2&c=true'], + [{ a: null, b: false, c: undefined }, 'a=&b=false'], + [{ 'a/b': 1.28341 }, `${encodeURIComponent('a/b')}=1.28341`], + [ + { 'a/b': 'c/d', 'e=f': 'g&h' }, + `${encodeURIComponent('a/b')}=${encodeURIComponent('c/d')}&${encodeURIComponent( + 'e=f', + )}=${encodeURIComponent('g&h')}`, + ], + ]) { + it(`${JSON.stringify(input)} -> ${expected}`, () => { + expect(stringifyQuery(input)).toEqual(expected); + }); + } + + for (const value of [[], {}, new Date()]) { + it(`${JSON.stringify(value)} -> `, () => { + expect(() => stringifyQuery({ value })).toThrow(`Cannot stringify type ${typeof value}`); + }); + } +}); diff --git a/tests/uploads.test.ts b/tests/uploads.test.ts new file mode 100644 index 00000000..63fa25fb --- /dev/null +++ b/tests/uploads.test.ts @@ -0,0 +1,107 @@ +import fs from 'fs'; +import type { ResponseLike } from 'terminal49/internal/to-file'; +import { toFile } from 'terminal49/core/uploads'; +import { File } from 'node:buffer'; + +class MyClass { + name: string = 'foo'; +} + +function mockResponse({ url, content }: { url: string; content?: Blob }): ResponseLike { + return { + url, + blob: async () => content || new Blob([]), + }; +} + +describe('toFile', () => { + it('throws a helpful error for mismatched types', async () => { + await expect( + // @ts-expect-error intentionally mismatched type + toFile({ foo: 'string' }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unexpected data type: object; constructor: Object; props: ["foo"]"`, + ); + + await expect( + // @ts-expect-error intentionally mismatched type + toFile(new MyClass()), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unexpected data type: object; constructor: MyClass; props: ["name"]"`, + ); + }); + + it('disallows string at the type-level', async () => { + // @ts-expect-error we intentionally do not type support for `string` + // to help people avoid passing a file path + const file = await toFile('contents'); + expect(file.text()).resolves.toEqual('contents'); + }); + + it('extracts a file name from a Response', async () => { + const response = mockResponse({ url: 'https://example.com/my/audio.mp3' }); + const file = await toFile(response); + expect(file.name).toEqual('audio.mp3'); + }); + + it('extracts a file name from a File', async () => { + const input = new File(['foo'], 'input.jsonl'); + const file = await toFile(input); + expect(file.name).toEqual('input.jsonl'); + }); + + it('extracts a file name from a ReadStream', async () => { + const input = fs.createReadStream('tests/uploads.test.ts'); + const file = await toFile(input); + expect(file.name).toEqual('uploads.test.ts'); + }); + + it('does not copy File objects', async () => { + const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' }); + const file = await toFile(input); + expect(file).toBe(input); + expect(file.name).toEqual('input.jsonl'); + expect(file.type).toBe('jsonl'); + }); + + it('is assignable to File and Blob', async () => { + const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' }); + const result = await toFile(input); + const file: File = result; + const blob: Blob = result; + void file, blob; + }); +}); + +describe('missing File error message', () => { + let prevGlobalFile: unknown; + let prevNodeFile: unknown; + beforeEach(() => { + // The file shim captures the global File object when it's first imported. + // Reset modules before each test so we can test the error thrown when it's undefined. + jest.resetModules(); + const buffer = require('node:buffer'); + // @ts-ignore + prevGlobalFile = globalThis.File; + prevNodeFile = buffer.File; + // @ts-ignore + globalThis.File = undefined; + buffer.File = undefined; + }); + afterEach(() => { + // Clean up + // @ts-ignore + globalThis.File = prevGlobalFile; + require('node:buffer').File = prevNodeFile; + jest.resetModules(); + }); + + test('is thrown', async () => { + const uploads = await import('terminal49/core/uploads'); + await expect( + uploads.toFile(mockResponse({ url: 'https://example.com/my/audio.mp3' })), + ).rejects.toMatchInlineSnapshot( + `[Error: \`File\` is not defined as a global, which is required for file uploads.]`, + ); + }); +}); diff --git a/tsc-multi.json b/tsc-multi.json new file mode 100644 index 00000000..384ddac5 --- /dev/null +++ b/tsc-multi.json @@ -0,0 +1,15 @@ +{ + "targets": [ + { + "extname": ".js", + "module": "commonjs", + "shareHelpers": "internal/tslib.js" + }, + { + "extname": ".mjs", + "module": "esnext", + "shareHelpers": "internal/tslib.mjs" + } + ], + "projects": ["tsconfig.build.json"] +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 00000000..09793524 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "include": ["dist/src"], + "exclude": [], + "compilerOptions": { + "rootDir": "./dist/src", + "paths": { + "terminal49/*": ["./dist/src/*"], + "terminal49": ["./dist/src/index.ts"] + }, + "noEmit": false, + "declaration": true, + "declarationMap": true, + "outDir": "dist", + "pretty": true, + "sourceMap": true + } +} diff --git a/tsconfig.deno.json b/tsconfig.deno.json new file mode 100644 index 00000000..849e070d --- /dev/null +++ b/tsconfig.deno.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "include": ["dist-deno"], + "exclude": [], + "compilerOptions": { + "rootDir": "./dist-deno", + "lib": ["es2020", "DOM"], + "noEmit": true, + "declaration": true, + "declarationMap": true, + "outDir": "dist-deno", + "pretty": true, + "sourceMap": true + } +} diff --git a/tsconfig.dist-src.json b/tsconfig.dist-src.json new file mode 100644 index 00000000..c550e299 --- /dev/null +++ b/tsconfig.dist-src.json @@ -0,0 +1,11 @@ +{ + // this config is included in the published src directory to prevent TS errors + // from appearing when users go to source, and VSCode opens the source .ts file + // via declaration maps + "include": ["index.ts"], + "compilerOptions": { + "target": "ES2015", + "lib": ["DOM", "DOM.Iterable", "ES2018"], + "moduleResolution": "node" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..1d0ac208 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,37 @@ +{ + "include": ["src", "tests", "examples"], + "exclude": [], + "compilerOptions": { + "target": "es2020", + "lib": ["es2020"], + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "paths": { + "terminal49/*": ["./src/*"], + "terminal49": ["./src/index.ts"] + }, + "noEmit": true, + + "resolveJsonModule": true, + + "forceConsistentCasingInFileNames": true, + + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "noImplicitReturns": true, + "alwaysStrict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "isolatedModules": false, + + "skipLibCheck": true + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..8311caf5 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,3500 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + +"@ampproject/remapping@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@andrewbranch/untar.js@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@andrewbranch/untar.js/-/untar.js-1.0.3.tgz#ba9494f85eb83017c5c855763969caf1d0adea00" + integrity sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw== + +"@arethetypeswrong/cli@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@arethetypeswrong/cli/-/cli-0.17.0.tgz#f97f10926b3f9f9eb5117550242d2e06c25cadac" + integrity sha512-xSMW7bfzVWpYw5JFgZqBXqr6PdR0/REmn3DkxCES5N0JTcB0CVgbIynJCvKBFmXaPc3hzmmTrb7+yPDRoOSZdA== + dependencies: + "@arethetypeswrong/core" "0.17.0" + chalk "^4.1.2" + cli-table3 "^0.6.3" + commander "^10.0.1" + marked "^9.1.2" + marked-terminal "^7.1.0" + semver "^7.5.4" + +"@arethetypeswrong/core@0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@arethetypeswrong/core/-/core-0.17.0.tgz#abb3b5f425056d37193644c2a2de4aecf866b76b" + integrity sha512-FHyhFizXNetigTVsIhqXKGYLpazPS5YNojEPpZEUcBPt9wVvoEbNIvG+hybuBR+pjlRcbyuqhukHZm1fr+bDgA== + dependencies: + "@andrewbranch/untar.js" "^1.0.3" + cjs-module-lexer "^1.2.3" + fflate "^0.8.2" + lru-cache "^10.4.3" + semver "^7.5.4" + typescript "5.6.1-rc" + validate-npm-package-name "^5.0.0" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + +"@babel/compat-data@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" + integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.6.tgz#8be77cd77c55baadcc1eae1c33df90ab6d2151d4" + integrity sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.23.6" + "@babel/parser" "^7.23.6" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.6" + "@babel/types" "^7.23.6" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.23.6", "@babel/generator@^7.7.2": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" + integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== + dependencies: + "@babel/types" "^7.23.6" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-imports@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-module-transforms@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + +"@babel/helpers@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.6.tgz#d03af2ee5fb34691eec0cda90f5ecbb4d4da145a" + integrity sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA== + dependencies: + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.6" + "@babel/types" "^7.23.6" + +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" + integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz#8f2e4f8a9b5f9aa16067e142c1ac9cd9f810f473" + integrity sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz#24f460c85dbbc983cd2b9c4994178bcc01df958f" + integrity sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/template@^7.22.15", "@babel/template@^7.3.3": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + +"@babel/traverse@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.6.tgz#b53526a2367a0dd6edc423637f3d2d0f2521abc5" + integrity sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.6" + "@babel/types" "^7.23.6" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.3.3": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" + integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@cspotcode/source-map-consumer@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" + integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== + +"@cspotcode/source-map-support@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" + integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== + dependencies: + "@cspotcode/source-map-consumer" "0.8.0" + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/config-array@^0.19.0": + version "0.19.2" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa" + integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w== + dependencies: + "@eslint/object-schema" "^2.1.6" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/core@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.10.0.tgz#23727063c21b335f752dbb3a16450f6f9cbc9091" + integrity sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/core@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.11.0.tgz#7a9226e850922e42cbd2ba71361eacbe74352a12" + integrity sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" + integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@9.20.0": + version "9.20.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.20.0.tgz#7421bcbe74889fcd65d1be59f00130c289856eb4" + integrity sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ== + +"@eslint/object-schema@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" + integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== + +"@eslint/plugin-kit@^0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz#ee07372035539e7847ef834e3f5e7b79f09e3a81" + integrity sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A== + dependencies: + "@eslint/core" "^0.10.0" + levn "^0.4.1" + +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.6" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" + integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.3.0" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" + integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== + +"@humanwhocodes/retry@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b" + integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/create-cache-key-function@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz#793be38148fab78e65f40ae30c36785f4ad859f0" + integrity sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA== + dependencies: + "@jest/types" "^29.6.3" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.20" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" + integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pkgr/core@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.4.tgz#d897170a2b0ba51f78a099edccd968f7b103387c" + integrity sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sindresorhus/is@^4.6.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + +"@sinonjs/commons@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" + integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@swc/core-darwin-arm64@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.16.tgz#2cd45d709ce76d448d96bf8d0006849541436611" + integrity sha512-UOCcH1GvjRnnM/LWT6VCGpIk0OhHRq6v1U6QXuPt5wVsgXnXQwnf5k3sG5Cm56hQHDvhRPY6HCsHi/p0oek8oQ== + +"@swc/core-darwin-x64@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.4.16.tgz#a5bc7d8b1dd850adb0bb95c6b5c742b92201fd01" + integrity sha512-t3bgqFoYLWvyVtVL6KkFNCINEoOrIlyggT/kJRgi1y0aXSr0oVgcrQ4ezJpdeahZZ4N+Q6vT3ffM30yIunELNA== + +"@swc/core-linux-arm-gnueabihf@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.16.tgz#961744908ee5cbb79bc009dcf58cc8b831111f38" + integrity sha512-DvHuwvEF86YvSd0lwnzVcjOTZ0jcxewIbsN0vc/0fqm9qBdMMjr9ox6VCam1n3yYeRtj4VFgrjeNFksqbUejdQ== + +"@swc/core-linux-arm64-gnu@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.16.tgz#43713be3f26757d82d2745dc25f8b63400e0a3d0" + integrity sha512-9Uu5YlPbyCvbidjKtYEsPpyZlu16roOZ5c2tP1vHfnU9bgf5Tz5q5VovSduNxPHx+ed2iC1b1URODHvDzbbDuQ== + +"@swc/core-linux-arm64-musl@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.16.tgz#394a7d030f3a61902bd3947bb9d70d26d42f3c81" + integrity sha512-/YZq/qB1CHpeoL0eMzyqK5/tYZn/rzKoCYDviFU4uduSUIJsDJQuQA/skdqUzqbheOXKAd4mnJ1hT04RbJ8FPQ== + +"@swc/core-linux-x64-gnu@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.16.tgz#71eb108b784f9d551ee8a35ebcdaed972f567981" + integrity sha512-UUjaW5VTngZYDcA8yQlrFmqs1tLi1TxbKlnaJwoNhel9zRQ0yG1YEVGrzTvv4YApSuIiDK18t+Ip927bwucuVQ== + +"@swc/core-linux-x64-musl@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.16.tgz#10dbaedb4e3dfc7268e3a9a66ad3431471ef035b" + integrity sha512-aFhxPifevDTwEDKPi4eRYWzC0p/WYJeiFkkpNU5Uc7a7M5iMWPAbPFUbHesdlb9Jfqs5c07oyz86u+/HySBNPQ== + +"@swc/core-win32-arm64-msvc@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.16.tgz#80247adff6c245ff32b44d773c1a148858cd655f" + integrity sha512-bTD43MbhIHL2s5QgCwyleaGwl96Gk/scF2TaVKdUe4QlJCDV/YK9h5oIBAp63ckHtE8GHlH4c8dZNBiAXn4Org== + +"@swc/core-win32-ia32-msvc@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.16.tgz#e540afc3ccf3224267b4ddfb408f9d9737984686" + integrity sha512-/lmZeAN/qV5XbK2SEvi8e2RkIg8FQNYiSA8y2/Zb4gTUMKVO5JMLH0BSWMiIKMstKDPDSxMWgwJaQHF8UMyPmQ== + +"@swc/core-win32-x64-msvc@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.16.tgz#f880939fca32c181adfe7e3abd2b6b7857bd3489" + integrity sha512-BPAfFfODWXtUu6SwaTTftDHvcbDyWBSI/oanUeRbQR5vVWkXoQ3cxLTsDluc3H74IqXS5z1Uyoe0vNo2hB1opA== + +"@swc/core@^1.3.102": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.4.16.tgz#d175bae2acfecd53bcbd4293f1fba5ec316634a0" + integrity sha512-Xaf+UBvW6JNuV131uvSNyMXHn+bh6LyKN4tbv7tOUFQpXyz/t9YWRE04emtlUW9Y0qrm/GKFCbY8n3z6BpZbTA== + dependencies: + "@swc/counter" "^0.1.2" + "@swc/types" "^0.1.5" + optionalDependencies: + "@swc/core-darwin-arm64" "1.4.16" + "@swc/core-darwin-x64" "1.4.16" + "@swc/core-linux-arm-gnueabihf" "1.4.16" + "@swc/core-linux-arm64-gnu" "1.4.16" + "@swc/core-linux-arm64-musl" "1.4.16" + "@swc/core-linux-x64-gnu" "1.4.16" + "@swc/core-linux-x64-musl" "1.4.16" + "@swc/core-win32-arm64-msvc" "1.4.16" + "@swc/core-win32-ia32-msvc" "1.4.16" + "@swc/core-win32-x64-msvc" "1.4.16" + +"@swc/counter@^0.1.2", "@swc/counter@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" + integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== + +"@swc/jest@^0.2.29": + version "0.2.36" + resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.36.tgz#2797450a30d28b471997a17e901ccad946fe693e" + integrity sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw== + dependencies: + "@jest/create-cache-key-function" "^29.7.0" + "@swc/counter" "^0.1.3" + jsonc-parser "^3.2.0" + +"@swc/types@^0.1.5": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.6.tgz#2f13f748995b247d146de2784d3eb7195410faba" + integrity sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg== + dependencies: + "@swc/counter" "^0.1.3" + +"@tsconfig/node10@^1.0.7": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" + integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== + +"@tsconfig/node12@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" + integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== + +"@tsconfig/node14@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" + integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== + +"@tsconfig/node16@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" + integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.4" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.4.tgz#ec2c06fed6549df8bc0eb4615b683749a4a92e1b" + integrity sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA== + dependencies: + "@babel/types" "^7.20.7" + +"@types/estree@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.4.0": + version "29.5.11" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.11.tgz#0c13aa0da7d0929f078ab080ae5d4ced80fa2f2c" + integrity sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/node@*": + version "20.10.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.5.tgz#47ad460b514096b7ed63a1dae26fad0914ed3ab2" + integrity sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw== + dependencies: + undici-types "~5.26.4" + +"@types/node@^20.17.6": + version "20.19.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.11.tgz#728cab53092bd5f143beed7fbba7ba99de3c16c4" + integrity sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow== + dependencies: + undici-types "~6.21.0" + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.32" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.32.tgz#030774723a2f7faafebf645f4e5a48371dca6229" + integrity sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz#62f1befe59647524994e89de4516d8dcba7a850a" + integrity sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/type-utils" "8.31.1" + "@typescript-eslint/utils" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/parser@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.31.1.tgz#e9b0ccf30d37dde724ee4d15f4dbc195995cce1b" + integrity sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q== + dependencies: + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/typescript-estree" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz#1eb52e76878f545e4add142e0d8e3e97e7aa443b" + integrity sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw== + dependencies: + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + +"@typescript-eslint/type-utils@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz#be0f438fb24b03568e282a0aed85f776409f970c" + integrity sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA== + dependencies: + "@typescript-eslint/typescript-estree" "8.31.1" + "@typescript-eslint/utils" "8.31.1" + debug "^4.3.4" + ts-api-utils "^2.0.1" + +"@typescript-eslint/types@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.31.1.tgz#478ed6f7e8aee1be7b63a60212b6bffe1423b5d4" + integrity sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ== + +"@typescript-eslint/typescript-estree@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz#37792fe7ef4d3021c7580067c8f1ae66daabacdf" + integrity sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag== + dependencies: + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/utils@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.31.1.tgz#5628ea0393598a0b2f143d0fc6d019f0dee9dd14" + integrity sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/typescript-estree" "8.31.1" + +"@typescript-eslint/visitor-keys@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz#6742b0e3ba1e0c1e35bdaf78c03e759eb8dd8e75" + integrity sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw== + dependencies: + "@typescript-eslint/types" "8.31.1" + eslint-visitor-keys "^4.2.0" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.14.0: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + +acorn@^8.4.1: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-escapes@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.0.0.tgz#00fc19f491bbb18e1d481b97868204f92109bfe7" + integrity sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw== + dependencies: + environment "^1.0.0" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.22.2: + version "4.22.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" + integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== + dependencies: + caniuse-lite "^1.0.30001565" + electron-to-chromium "^1.4.601" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001565: + version "1.0.30001570" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz#b4e5c1fa786f733ab78fc70f592df6b3f23244ca" + integrity sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw== + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" + integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== + +cjs-module-lexer@^1.2.3: + version "1.4.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" + integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-highlight@^2.1.11: + version "2.1.11" + resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" + integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== + dependencies: + chalk "^4.0.0" + highlight.js "^10.7.1" + mz "^2.4.0" + parse5 "^5.1.1" + parse5-htmlparser2-tree-adapter "^6.0.0" + yargs "^16.0.0" + +cli-table3@^0.6.3, cli-table3@^0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" + integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.3, cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +debug@^4.3.4, debug@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + +dedent@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" + integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +electron-to-chromium@^1.4.601: + version "1.4.614" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.614.tgz#2fe789d61fa09cb875569f37c309d0c2701f91c0" + integrity sha512-X4ze/9Sc3QWs6h92yerwqv7aB/uU8vCjZcrMjA8N9R1pjMFRe44dLsck5FzLilOYvcXuDn93B+bpGYyufc70gQ== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emojilib@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/emojilib/-/emojilib-2.4.0.tgz#ac518a8bb0d5f76dda57289ccb2fdf9d39ae721e" + integrity sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw== + +environment@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" + integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-plugin-prettier@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.1.tgz#99b55d7dd70047886b2222fdd853665f180b36af" + integrity sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.11.7" + +eslint-plugin-unused-imports@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz#62ddc7446ccbf9aa7b6f1f0b00a980423cda2738" + integrity sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ== + +eslint-scope@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" + integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + +eslint@^9.20.1: + version "9.20.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.20.1.tgz#923924c078f5226832449bac86662dd7e53c91d6" + integrity sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.19.0" + "@eslint/core" "^0.11.0" + "@eslint/eslintrc" "^3.2.0" + "@eslint/js" "9.20.0" + "@eslint/plugin-kit" "^0.2.5" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.1" + "@types/estree" "^1.0.6" + "@types/json-schema" "^7.0.15" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.2.0" + eslint-visitor-keys "^4.2.0" + espree "^10.3.0" + esquery "^1.5.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^10.0.1, espree@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" + integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== + dependencies: + acorn "^8.14.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fflate@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + +flatted@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" + integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stdin@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^8.0.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + +highlight.js@^10.7.1: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ignore-walk@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776" + integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw== + dependencies: + minimatch "^5.0.1" + +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz#71e87707e8041428732518c6fb5211761753fbdf" + integrity sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.6" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" + integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.4.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonc-parser@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a" + integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA== + +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lru-cache@^10.4.3: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@1.x, make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +marked-terminal@^7.1.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-7.2.1.tgz#9c1ae073a245a03c6a13e3eeac6f586f29856068" + integrity sha512-rQ1MoMFXZICWNsKMiiHwP/Z+92PLKskTPXj+e7uwXmuMPkNn7iTqC+IvDekVm1MPeC9wYQeLxeFaOvudRR/XbQ== + dependencies: + ansi-escapes "^7.0.0" + ansi-regex "^6.1.0" + chalk "^5.3.0" + cli-highlight "^2.1.11" + cli-table3 "^0.6.5" + node-emoji "^2.1.3" + supports-hyperlinks "^3.1.0" + +marked@^9.1.2: + version "9.1.6" + resolved "https://registry.yarnpkg.com/marked/-/marked-9.1.6.tgz#5d2a3f8180abfbc5d62e3258a38a1c19c0381695" + integrity sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + +mri@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" + integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +node-emoji@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-2.1.3.tgz#93cfabb5cc7c3653aa52f29d6ffb7927d8047c06" + integrity sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA== + dependencies: + "@sindresorhus/is" "^4.6.0" + char-regex "^1.0.2" + emojilib "^2.4.0" + skin-tone "^2.0.0" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-bundled@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-2.0.1.tgz#94113f7eb342cd7a67de1e789f896b04d2c600f4" + integrity sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw== + dependencies: + npm-normalize-package-bin "^2.0.0" + +npm-normalize-package-bin@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz#9447a1adaaf89d8ad0abe24c6c84ad614a675fff" + integrity sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ== + +npm-packlist@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.3.tgz#69d253e6fd664b9058b85005905012e00e69274b" + integrity sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg== + dependencies: + glob "^8.0.1" + ignore-walk "^5.0.1" + npm-bundled "^2.0.0" + npm-normalize-package-bin "^2.0.0" + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + +p-all@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-all/-/p-all-3.0.0.tgz#077c023c37e75e760193badab2bad3ccd5782bfb" + integrity sha512-qUZbvbBFVXm6uJ7U/WDiO0fv6waBMbjlCm4E66oZdRR+egswICarIdHyVSZZHudH8T5SF8x/JG0q0duFzPnlBw== + dependencies: + p-map "^4.0.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse5-htmlparser2-tree-adapter@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + +parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.1.tgz#6ba9f23165d690b6cbdaa88cb0807278f7019848" + integrity sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +publint@^0.2.12: + version "0.2.12" + resolved "https://registry.yarnpkg.com/publint/-/publint-0.2.12.tgz#d25cd6bd243d5bdd640344ecdddb3eeafdcc4059" + integrity sha512-YNeUtCVeM4j9nDiTT2OPczmlyzOkIXNtdDZnSuajAxS/nZ6j3t7Vs9SUB4euQNddiltIwu7Tdd3s+hr08fAsMw== + dependencies: + npm-packlist "^5.1.3" + picocolors "^1.1.1" + sade "^1.8.1" + +punycode@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +pure-rand@^6.0.0: + version "6.0.4" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.4.tgz#50b737f6a925468679bff00ad20eade53f37d5c7" + integrity sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + +readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== + +resolve@^1.20.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +sade@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" + integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== + dependencies: + mri "^1.1.0" + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.3: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +semver@^7.5.4: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +semver@^7.6.0: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +skin-tone@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/skin-tone/-/skin-tone-2.0.0.tgz#4e3933ab45c0d4f4f781745d64b9f4c208e41237" + integrity sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA== + dependencies: + unicode-emoji-modifier-base "^1.0.0" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-to-stream@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/string-to-stream/-/string-to-stream-3.0.1.tgz#480e6fb4d5476d31cb2221f75307a5dcb6638a42" + integrity sha512-Hl092MV3USJuUCC6mfl9sPzGloA3K5VwdIeJjYIkXY/8K+mUvaeEabWJgArp+xXrsWxCajeT2pc4axbVhIZJyg== + dependencies: + readable-stream "^3.4.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +superstruct@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" + integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz#b56150ff0173baacc15f21956450b61f2b18d3ac" + integrity sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +synckit@^0.11.7: + version "0.11.8" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.8.tgz#b2aaae998a4ef47ded60773ad06e7cb821f55457" + integrity sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A== + dependencies: + "@pkgr/core" "^0.2.4" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-api-utils@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.0.1.tgz#660729385b625b939aaa58054f45c058f33f10cd" + integrity sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w== + +ts-jest@^29.1.0: + version "29.1.1" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" + integrity sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "4.x" + make-error "1.x" + semver "^7.5.3" + yargs-parser "^21.0.1" + +ts-node@^10.5.0: + version "10.7.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5" + integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A== + dependencies: + "@cspotcode/source-map-support" "0.7.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.0" + yn "3.1.1" + +"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz": + version "1.1.9" + resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz#777f6f5d9e26bf0e94e5170990dd3a841d6707cd" + dependencies: + debug "^4.3.7" + fast-glob "^3.3.2" + get-stdin "^8.0.0" + p-all "^3.0.0" + picocolors "^1.1.1" + signal-exit "^3.0.7" + string-to-stream "^3.0.1" + superstruct "^1.0.4" + tslib "^2.8.1" + yargs "^17.7.2" + +tsconfig-paths@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typescript-eslint@8.31.1: + version "8.31.1" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.31.1.tgz#b77ab1e48ced2daab9225ff94bab54391a4af69b" + integrity sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA== + dependencies: + "@typescript-eslint/eslint-plugin" "8.31.1" + "@typescript-eslint/parser" "8.31.1" + "@typescript-eslint/utils" "8.31.1" + +typescript@5.6.1-rc: + version "5.6.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.1-rc.tgz#d5e4d7d8170174fed607b74cc32aba3d77018e02" + integrity sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ== + +typescript@5.8.3: + version "5.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" + integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +unicode-emoji-modifier-base@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz#dbbd5b54ba30f287e2a8d5a249da6c0cef369459" + integrity sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g== + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +v8-compile-cache-lib@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz#0582bcb1c74f3a2ee46487ceecf372e46bce53e8" + integrity sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA== + +v8-to-istanbul@^9.0.1: + version "9.2.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" + integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +validate-npm-package-name@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz#a316573e9b49f3ccd90dbb6eb52b3f06c6d604e8" + integrity sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ== + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-parser@^21.0.1, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^16.0.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^17.3.1, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==