From c767494ae42a57ed7ec9ac94ebcbdb8736e02446 Mon Sep 17 00:00:00 2001 From: Ravi Tharuma <25951435+RaviTharuma@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:52:24 +0200 Subject: [PATCH] feat(api): alias /v1/multimodal-embeddings to /v1/embeddings (#10568) Jina-compatible clients POST /v1/multimodal-embeddings and currently get HTTP 404 unknown_route from the catch-all. Co-authored-by: Ravi Tharuma Co-authored-by: Cursor --- .../features/multimodal-embeddings-alias.md | 1 + docs/openapi.yaml | 32 +++++++++++++++++++ src/app/api/v1/multimodal-embeddings/route.ts | 5 +++ src/shared/constants/endpointCategories.ts | 2 +- tests/unit/endpoint-categories.test.ts | 4 +++ .../unit/multimodal-embeddings-alias.test.ts | 16 ++++++++++ 6 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 changelog.d/features/multimodal-embeddings-alias.md create mode 100644 src/app/api/v1/multimodal-embeddings/route.ts create mode 100644 tests/unit/multimodal-embeddings-alias.test.ts diff --git a/changelog.d/features/multimodal-embeddings-alias.md b/changelog.d/features/multimodal-embeddings-alias.md new file mode 100644 index 0000000000..b69c53ecb5 --- /dev/null +++ b/changelog.d/features/multimodal-embeddings-alias.md @@ -0,0 +1 @@ +- **feat(api):** add `GET`/`POST` `/v1/multimodal-embeddings` as an alias of `/v1/embeddings` so Jina-compatible clients do not receive HTTP 404 `unknown_route` — thanks @RaviTharuma diff --git a/docs/openapi.yaml b/docs/openapi.yaml index eae1b2859f..fc5f85c60b 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1371,6 +1371,38 @@ paths: cost is computed per modality when pricing is available, otherwise `0` (fail-open). + /api/v1/multimodal-embeddings: + post: + tags: [Embeddings] + summary: Create embeddings (Jina multimodal-embeddings alias) + description: >- + Same handler as `POST /api/v1/embeddings`. Provided so Jina-compatible + clients that call `/v1/multimodal-embeddings` do not receive HTTP 404 + `unknown_route`. + security: + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [input, model] + additionalProperties: true + responses: + "200": + description: Embedding vectors (same contract as POST /api/v1/embeddings). + "401": + $ref: "#/components/responses/Unauthorized" + get: + tags: [Embeddings] + summary: List embedding models (Jina multimodal-embeddings alias) + security: + - BearerAuth: [] + responses: + "200": + description: Embedding model catalog (same as GET /api/v1/embeddings). + /api/v1/providers/{provider}/embeddings: post: tags: [Embeddings] diff --git a/src/app/api/v1/multimodal-embeddings/route.ts b/src/app/api/v1/multimodal-embeddings/route.ts new file mode 100644 index 0000000000..2182c9cb6a --- /dev/null +++ b/src/app/api/v1/multimodal-embeddings/route.ts @@ -0,0 +1,5 @@ +/** + * Alias of `/v1/embeddings` for clients that call Jina's + * `/v1/multimodal-embeddings` path. Same handler, same catalog GET. + */ +export { GET, POST, OPTIONS } from "../embeddings/route"; diff --git a/src/shared/constants/endpointCategories.ts b/src/shared/constants/endpointCategories.ts index abaaeb7a2f..9976af5120 100644 --- a/src/shared/constants/endpointCategories.ts +++ b/src/shared/constants/endpointCategories.ts @@ -34,7 +34,7 @@ export const ENDPOINT_CATEGORIES: readonly EndpointCategory[] = [ id: "embeddings", label: "Embeddings", description: "Text embeddings generation", - prefixes: ["/v1/embeddings"], + prefixes: ["/v1/embeddings", "/v1/multimodal-embeddings"], }, { id: "images", diff --git a/tests/unit/endpoint-categories.test.ts b/tests/unit/endpoint-categories.test.ts index b51215300d..3ff73d0af2 100644 --- a/tests/unit/endpoint-categories.test.ts +++ b/tests/unit/endpoint-categories.test.ts @@ -44,6 +44,10 @@ test("resolveEndpointCategory: maps /v1/embeddings to 'embeddings'", () => { assert.equal(resolveEndpointCategory("/v1/embeddings"), "embeddings"); }); +test("resolveEndpointCategory: maps /v1/multimodal-embeddings to 'embeddings'", () => { + assert.equal(resolveEndpointCategory("/v1/multimodal-embeddings"), "embeddings"); +}); + test("resolveEndpointCategory: maps /v1/images/generations to 'images'", () => { assert.equal(resolveEndpointCategory("/v1/images/generations"), "images"); }); diff --git a/tests/unit/multimodal-embeddings-alias.test.ts b/tests/unit/multimodal-embeddings-alias.test.ts new file mode 100644 index 0000000000..a238b4adf7 --- /dev/null +++ b/tests/unit/multimodal-embeddings-alias.test.ts @@ -0,0 +1,16 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +process.env.DATA_DIR = mkdtempSync(join(tmpdir(), "omniroute-mm-embed-alias-")); + +const embeddings = await import("../../src/app/api/v1/embeddings/route.ts"); +const multimodal = await import("../../src/app/api/v1/multimodal-embeddings/route.ts"); + +test("POST/GET/OPTIONS /v1/multimodal-embeddings are the embeddings handlers", () => { + assert.equal(multimodal.POST, embeddings.POST); + assert.equal(multimodal.GET, embeddings.GET); + assert.equal(multimodal.OPTIONS, embeddings.OPTIONS); +});