fix(providers): repoint freeaiapikey to its live API host and resync its catalog (#10233)

* fix(providers): point freeaiapikey at the api. host it moved to

Every /v1 route on the freeaiapikey.com apex host answers HTTP 410 with
type "endpoint_moved", and the body names its own replacement:

  "This API endpoint has moved. Please update your base_url to
   https://api.freeaiapikey.com/v1 - the old endpoint on freeaiapikey.com
   no longer works."

Probed 2026-08-13 with paired controls so a network fault could not be
read as an upstream verdict:

  GET https://freeaiapikey.com/v1/models                -> 410
  GET https://freeaiapikey.com/v1/chat/completions      -> 410
  GET https://api.freeaiapikey.com/v1/models            -> 200
  GET https://api.freeaiapikey.com/v1/chat/completions  -> 405 (POST-only)
  GET https://api.openai.com/v1/models                  -> 401 (control: reachable)
  GET https://<nonexistent-domain>/v1/models            -> 000 (control: unreachable)

Every request through this provider therefore fails today. Repoint baseUrl
and modelsUrl at the host upstream names.

* fix(providers): resync the freeaiapikey catalog with its live model list

GET https://api.freeaiapikey.com/v1/models (200, probed 2026-08-13) serves 10
models. The registry declared 7, four of which upstream does not serve at all:
openai/gpt-5, openai/gpt-5.2-codex, Alibaba/qwen3.5, Alibaba/qwen3-vl:235b.
Seven live models were missing: openai/gpt-5.4, openai/gpt-5.5,
openai/gpt-5.6-sol, anthropic/claude-opus-4.7, anthropic/claude-opus-4.8,
anthropic/claude-sonnet-5, anthropic/claude-opus-5.

The four phantom ids are selectable in the dashboard and can only ever fail
upstream; the seven real ones are unreachable through the static catalog.

On context windows: the /v1/models response carries only id/object/created/
owned_by, so upstream publishes no window at all. The models added here
therefore declare no contextLength and inherit the entry's existing
defaultContextLength (128000) instead of a fabricated number. The two
pre-existing contextLength values are left untouched for the same reason -
this sweep neither confirms nor refutes them, and rewriting them would be
guesswork in the other direction.

* chore(changelog): name the fragment after the real PR number

* chore(changelog): substitute the PRNUM placeholder in the fragment body

---------

Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
This commit is contained in:
Paco Cartones
2026-08-16 05:42:25 +02:00
committed by GitHub
parent be6f18b849
commit d010a9979f
4 changed files with 126 additions and 16 deletions

View File

@@ -0,0 +1 @@
- **fix(providers):** FreeAIAPIKey now targets `api.freeaiapikey.com`, the host upstream names in its `410 endpoint_moved` response — every request through the provider was failing — and its catalog is resynced to the 10 models the live `/v1/models` actually serves ([#10233](https://github.com/diegosouzapw/OmniRoute/pull/10233))

View File

@@ -5,34 +5,39 @@ export const freeaiapikeyProvider: RegistryEntry = {
alias: "faik",
format: "openai",
executor: "default",
baseUrl: "https://freeaiapikey.com/v1/chat/completions",
modelsUrl: "https://freeaiapikey.com/v1/models",
// 2026-08-13: the apex host answers 410 `endpoint_moved` on every /v1 route and
// names its own replacement — "Please update your base_url to
// https://api.freeaiapikey.com/v1". The api. host serves /v1/models (200) and
// /v1/chat/completions (405 on GET, i.e. POST-only as expected).
baseUrl: "https://api.freeaiapikey.com/v1/chat/completions",
modelsUrl: "https://api.freeaiapikey.com/v1/models",
authType: "apikey",
authHeader: "bearer",
defaultContextLength: 128000,
// Catalog synced 2026-08-13 against GET https://api.freeaiapikey.com/v1/models (200).
// That response carries only id/object/created/owned_by — upstream publishes no
// context window — so models added from it declare no contextLength and inherit
// `defaultContextLength` above rather than an invented figure. The two pre-existing
// contextLength values are left exactly as they were: nothing in this sweep confirms
// or refutes them, and rewriting them would be the same guesswork in reverse.
models: [
{ id: "openai/gpt-5", name: "GPT-5 (via FreeAIAPIKey)", contextLength: 400000 },
{ id: "openai/gpt-4o", name: "GPT-4o (via FreeAIAPIKey)" },
{ id: "openai/gpt-5.2-codex", name: "GPT-5.2 Codex (via FreeAIAPIKey)" },
{ id: "openai/gpt-5.4", name: "GPT-5.4 (via FreeAIAPIKey)" },
{ id: "openai/gpt-5.5", name: "GPT-5.5 (via FreeAIAPIKey)" },
{ id: "openai/gpt-5.6-sol", name: "GPT-5.6 Sol (via FreeAIAPIKey)" },
{
id: "anthropic/claude-opus-4.6",
name: "Claude Opus 4.6 (via FreeAIAPIKey)",
contextLength: 1000000,
},
{ id: "anthropic/claude-opus-4.7", name: "Claude Opus 4.7 (via FreeAIAPIKey)" },
{ id: "anthropic/claude-opus-4.8", name: "Claude Opus 4.8 (via FreeAIAPIKey)" },
{ id: "anthropic/claude-opus-5", name: "Claude Opus 5 (via FreeAIAPIKey)" },
{
id: "anthropic/claude-sonnet-4.6",
name: "Claude Sonnet 4.6 (via FreeAIAPIKey)",
contextLength: 1000000,
},
{
id: "Alibaba/qwen3.5",
name: "Qwen 3.5 (via FreeAIAPIKey)",
contextLength: 128000,
},
{
id: "Alibaba/qwen3-vl:235b",
name: "Qwen 3 VL 235B (via FreeAIAPIKey)",
contextLength: 128000,
},
{ id: "anthropic/claude-sonnet-5", name: "Claude Sonnet 5 (via FreeAIAPIKey)" },
],
};

View File

@@ -2082,8 +2082,8 @@
}
},
"url": {
"nonStream": "https://freeaiapikey.com/v1/chat/completions",
"stream": "https://freeaiapikey.com/v1/chat/completions"
"nonStream": "https://api.freeaiapikey.com/v1/chat/completions",
"stream": "https://api.freeaiapikey.com/v1/chat/completions"
}
},
"freeinference": {

View File

@@ -0,0 +1,104 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import { freeaiapikeyProvider } from "../../open-sse/config/providers/registry/freeaiapikey/index.ts";
/**
* FreeAIAPIKey retired its apex-host API and moved it to a dedicated `api.` host.
*
* Live probe (2026-08-13), each paired with a control call so a network fault
* cannot be mistaken for an upstream verdict:
*
* GET https://freeaiapikey.com/v1/models → 410
* GET https://freeaiapikey.com/v1/chat/completions → 410
* GET https://api.freeaiapikey.com/v1/models → 200
* GET https://api.freeaiapikey.com/v1/chat/completions → 405 (POST-only endpoint)
* GET https://api.openai.com/v1/models → 401 (control: reachable)
* GET https://<nonexistent-domain>/v1/models → 000 (control: unreachable)
*
* The 410 body names its own replacement, so the target host is upstream's own
* instruction rather than an inference:
*
* {"error":{"message":"This API endpoint has moved. Please update your base_url
* to https://api.freeaiapikey.com/v1 — the old endpoint on freeaiapikey.com no
* longer works.","type":"endpoint_moved","code":"endpoint_moved"}}
*
* Provider entry added in #2708.
*/
const LIVE_API_BASE = "https://api.freeaiapikey.com/v1";
/**
* Every model id returned by GET https://api.freeaiapikey.com/v1/models on 2026-08-13.
* The response carries only id/object/created/owned_by — upstream publishes no context
* window, so models catalogued from it declare no contextLength and inherit the entry's
* defaultContextLength rather than an invented number.
*/
const LIVE_MODEL_IDS = [
"openai/gpt-4o",
"openai/gpt-5.4",
"openai/gpt-5.5",
"openai/gpt-5.6-sol",
"anthropic/claude-opus-4.6",
"anthropic/claude-opus-4.7",
"anthropic/claude-opus-4.8",
"anthropic/claude-sonnet-4.6",
"anthropic/claude-sonnet-5",
"anthropic/claude-opus-5",
];
test("freeaiapikey targets the live api. host (upstream 410 endpoint_moved)", () => {
assert.equal(
freeaiapikeyProvider.baseUrl,
`${LIVE_API_BASE}/chat/completions`,
"baseUrl must point at the host named in upstream's 410 endpoint_moved body"
);
assert.equal(
freeaiapikeyProvider.modelsUrl,
`${LIVE_API_BASE}/models`,
"modelsUrl must point at the host named in upstream's 410 endpoint_moved body"
);
});
test("freeaiapikey keeps no endpoint on the retired freeaiapikey.com apex host", () => {
for (const [field, url] of [
["baseUrl", freeaiapikeyProvider.baseUrl],
["modelsUrl", freeaiapikeyProvider.modelsUrl],
] as const) {
assert.ok(url, `${field} must be set`);
assert.doesNotMatch(
url,
/^https:\/\/freeaiapikey\.com\//,
`${field} still targets the apex host, which answers 410 endpoint_moved`
);
}
});
test("freeaiapikey catalogs exactly the models upstream serves", () => {
const declared = freeaiapikeyProvider.models.map((model) => model.id);
assert.deepEqual(
[...declared].sort(),
[...LIVE_MODEL_IDS].sort(),
"registry catalog must match the ids returned by the live /v1/models"
);
});
test("freeaiapikey declares no duplicate model ids", () => {
const declared = freeaiapikeyProvider.models.map((model) => model.id);
assert.equal(new Set(declared).size, declared.length, "model ids must be unique");
});
test("freeaiapikey gives every catalogued model a display name", () => {
for (const model of freeaiapikeyProvider.models) {
assert.equal(typeof model.name, "string", `${model.id} must declare a name`);
assert.ok(model.name.length > 0, `${model.id} must declare a non-empty name`);
}
});
test("freeaiapikey keeps a provider-wide default for unpublished context windows", () => {
// Upstream reports no context windows, so the models added from its catalog carry
// no contextLength of their own; this default is what they fall back to.
assert.equal(
typeof freeaiapikeyProvider.defaultContextLength,
"number",
"entry must keep a defaultContextLength for models with no upstream-published window"
);
});