From 2f4e0c5473ff6b91ea3c9fae18c5daf557454ed4 Mon Sep 17 00:00:00 2001 From: Tong Demag Date: Sun, 1 Nov 2026 21:30:38 +0800 Subject: [PATCH 1/2] feat(providers): add APIKO as an OpenAI-compatible provider Co-Authored-By: Claude Opus 5.5 --- litellm/llms/openai_like/providers.json | 8 +++ .../provider_endpoints_support_backup.json | 26 ++++++++ .../provider_create_fields.json | 28 +++++++++ litellm/types/utils.py | 1 + provider_endpoints_support.json | 26 ++++++++ .../llms/openai_like/test_apiko_provider.py | 60 +++++++++++++++++++ 6 files changed, 149 insertions(+) create mode 100644 tests/unit/llms/openai_like/test_apiko_provider.py diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index a71a9c3bb9b..f0788ae93dd 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -287,6 +287,14 @@ "supports_function_calling": true } }, + "apiko": { + "base_url": "https://api.apiko.app/v1", + "api_key_env": "APIKO_API_KEY", + "api_base_env": "APIKO_API_BASE", + "supported_endpoints": [ + "/v1/chat/completions" + ] + }, "streamlake": { "base_url": "https://vanchin.streamlake.ai/api/gateway/v1/endpoints", "api_key_env": "STREAMLAKE_API_KEY", diff --git a/litellm/provider_endpoints_support_backup.json b/litellm/provider_endpoints_support_backup.json index 700a35edb06..f1a057f126e 100644 --- a/litellm/provider_endpoints_support_backup.json +++ b/litellm/provider_endpoints_support_backup.json @@ -5458,6 +5458,32 @@ "generateContent": true } }, + "apiko": { + "display_name": "APIKO (`apiko`)", + "url": "https://docs.litellm.ai/docs/providers/apiko", + "endpoints": { + "chat_completions": true, + "responses": false, + "text_completion": false, + "embeddings": false, + "image_generations": false, + "image_edits": false, + "audio_transcriptions": false, + "audio_speech": false, + "rerank": false, + "ocr": false, + "search": false, + "moderations": false, + "realtime": false, + "video_generations": false, + "batches": false, + "messages": false, + "interactions": false, + "systemone": false, + "a2a": false, + "generateContent": false + } + }, "bigmodel": { "display_name": "BigModel (Zhipu AI) (`bigmodel`)", "url": "https://docs.bigmodel.cn/cn/guide/develop/responses/introduction", diff --git a/litellm/proxy/public_endpoints/provider_create_fields.json b/litellm/proxy/public_endpoints/provider_create_fields.json index 044b8a1de6e..37257596998 100644 --- a/litellm/proxy/public_endpoints/provider_create_fields.json +++ b/litellm/proxy/public_endpoints/provider_create_fields.json @@ -535,6 +535,34 @@ ], "default_model_placeholder": "gpt-3.5-turbo" }, + { + "provider": "APIKO", + "provider_display_name": "APIKO", + "litellm_provider": "apiko", + "credential_fields": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://api.apiko.app/v1", + "tooltip": null, + "required": false, + "field_type": "text", + "options": null, + "default_value": null + }, + { + "key": "api_key", + "label": "API Key", + "placeholder": null, + "tooltip": null, + "required": true, + "field_type": "password", + "options": null, + "default_value": null + } + ], + "default_model_placeholder": "apiko/deepseek-v4-pro" + }, { "provider": "AssemblyAI", "provider_display_name": "AssemblyAI", diff --git a/litellm/types/utils.py b/litellm/types/utils.py index da1d99957a7..d314895abd0 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -4673,6 +4673,7 @@ class LlmProviders(str, Enum): MICROSOFT_365_COPILOT = "microsoft_365_copilot" MINIMAX = "minimax" SYNTHETIC = "synthetic" + APIKO = "apiko" APERTIS = "apertis" NANOGPT = "nano-gpt" POE = "poe" diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 700a35edb06..f1a057f126e 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -5458,6 +5458,32 @@ "generateContent": true } }, + "apiko": { + "display_name": "APIKO (`apiko`)", + "url": "https://docs.litellm.ai/docs/providers/apiko", + "endpoints": { + "chat_completions": true, + "responses": false, + "text_completion": false, + "embeddings": false, + "image_generations": false, + "image_edits": false, + "audio_transcriptions": false, + "audio_speech": false, + "rerank": false, + "ocr": false, + "search": false, + "moderations": false, + "realtime": false, + "video_generations": false, + "batches": false, + "messages": false, + "interactions": false, + "systemone": false, + "a2a": false, + "generateContent": false + } + }, "bigmodel": { "display_name": "BigModel (Zhipu AI) (`bigmodel`)", "url": "https://docs.bigmodel.cn/cn/guide/develop/responses/introduction", diff --git a/tests/unit/llms/openai_like/test_apiko_provider.py b/tests/unit/llms/openai_like/test_apiko_provider.py new file mode 100644 index 00000000000..df6bbd57537 --- /dev/null +++ b/tests/unit/llms/openai_like/test_apiko_provider.py @@ -0,0 +1,60 @@ +""" +Unit tests for the APIKO OpenAI-compatible provider. +""" + +import os +import sys + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../.."))) + +import litellm +from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider +from litellm.llms.openai_like.dynamic_config import create_config_class +from litellm.llms.openai_like.json_loader import JSONProviderRegistry + +APIKO_BASE_URL = "https://api.apiko.app/v1" + + +def _get_config(): + provider = JSONProviderRegistry.get("apiko") + assert provider is not None + return create_config_class(provider)() + + +def test_apiko_provider_registered(): + provider = JSONProviderRegistry.get("apiko") + assert provider is not None + assert provider.base_url == APIKO_BASE_URL + assert provider.api_key_env == "APIKO_API_KEY" + assert provider.api_base_env == "APIKO_API_BASE" + assert litellm.LlmProviders.APIKO.value == "apiko" + + +def test_apiko_resolves_env_api_key(monkeypatch): + monkeypatch.delenv("APIKO_API_BASE", raising=False) + monkeypatch.setenv("APIKO_API_KEY", "test-key") + api_base, api_key = _get_config().get_openai_compatible_provider_info(None, None) + assert api_base == APIKO_BASE_URL + assert api_key == "test-key" + + +def test_apiko_model_prefix_routes_to_provider(monkeypatch): + monkeypatch.delenv("APIKO_API_BASE", raising=False) + monkeypatch.setenv("APIKO_API_KEY", "test-key") + model, provider, api_key, api_base = get_llm_provider("apiko/deepseek-v4-pro") + assert model == "deepseek-v4-pro" + assert provider == "apiko" + assert api_key == "test-key" + assert api_base == APIKO_BASE_URL + + +def test_apiko_complete_url_appends_endpoint(): + url = _get_config().get_complete_url( + api_base=APIKO_BASE_URL, + api_key="test-key", + model="apiko/deepseek-v4-pro", + optional_params={}, + litellm_params={}, + stream=False, + ) + assert url == f"{APIKO_BASE_URL}/chat/completions" From 8c3e70f1cb788db2cb976788377f8184a69227f9 Mon Sep 17 00:00:00 2001 From: Tong Demag Date: Sun, 1 Nov 2026 22:06:50 +0800 Subject: [PATCH 2/2] fix(apiko): require an APIKO key instead of falling back to OpenAI credentials Co-Authored-By: Claude Opus 5.5 --- litellm/main.py | 10 +++++++++- tests/unit/llms/openai_like/test_apiko_provider.py | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/litellm/main.py b/litellm/main.py index 78767b03eb7..807ef89f489 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -2866,9 +2866,17 @@ def _complete_custom_openai( raise litellm.AuthenticationError( message="Set BIGMODEL_API_KEY or pass api_key", llm_provider="bigmodel", model=model ) + if custom_llm_provider == "apiko" and not api_key: + raise litellm.AuthenticationError( + message="Set APIKO_API_KEY or pass api_key", llm_provider="apiko", model=model + ) organization = ( organization - or (None if custom_llm_provider == "bigmodel" else litellm.organization or get_secret("OPENAI_ORGANIZATION")) + or ( + None + if custom_llm_provider in ("bigmodel", "apiko") + else litellm.organization or get_secret("OPENAI_ORGANIZATION") + ) or None # default - https://github.com/openai/openai-python/blob/284c1799070c723c6a553337134148a7ab088dd8/openai/util.py#L105 ) openai.organization = organization diff --git a/tests/unit/llms/openai_like/test_apiko_provider.py b/tests/unit/llms/openai_like/test_apiko_provider.py index df6bbd57537..5a2a1e59e2c 100644 --- a/tests/unit/llms/openai_like/test_apiko_provider.py +++ b/tests/unit/llms/openai_like/test_apiko_provider.py @@ -7,6 +7,8 @@ import sys sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../.."))) +import pytest + import litellm from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider from litellm.llms.openai_like.dynamic_config import create_config_class @@ -58,3 +60,15 @@ def test_apiko_complete_url_appends_endpoint(): stream=False, ) assert url == f"{APIKO_BASE_URL}/chat/completions" + + +def test_apiko_does_not_fall_back_to_openai_key(monkeypatch): + monkeypatch.delenv("APIKO_API_KEY", raising=False) + monkeypatch.setenv("OPENAI_API_KEY", "sk-unrelated-openai-key") + monkeypatch.setattr(litellm, "api_key", None) + monkeypatch.setattr(litellm, "openai_key", None) + with pytest.raises(litellm.AuthenticationError, match="Set APIKO_API_KEY"): + litellm.completion( + model="apiko/deepseek-v4-pro", + messages=[{"role": "user", "content": "hi"}], + )