diff --git a/docs/configuration.md b/docs/configuration.md
index fed07ff2d..eaa3626b2 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -134,6 +134,7 @@ ANTHROPIC_API_KEY="$(bw get password api/anthropic)" nanobot agent
| `custom` | Any OpenAI-compatible endpoint | — |
| `openrouter` | LLM (recommended, access to all models) | [openrouter.ai](https://openrouter.ai) |
| `huggingface` | LLM (Hugging Face Inference Providers) | [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) |
+| `skywork` | LLM (Skywork / APIFree API gateway) | [apifree.ai](https://www.apifree.ai) |
| `volcengine` | LLM (VolcEngine, pay-per-use) | [Coding Plan](https://www.volcengine.com/activity/codingplan?utm_campaign=nanobot&utm_content=nanobot&utm_medium=devrel&utm_source=OWO&utm_term=nanobot) · [volcengine.com](https://www.volcengine.com) |
| `byteplus` | LLM (VolcEngine international, pay-per-use) | [Coding Plan](https://www.byteplus.com/en/activity/codingplan?utm_campaign=nanobot&utm_content=nanobot&utm_medium=devrel&utm_source=OWO&utm_term=nanobot) · [byteplus.com](https://www.byteplus.com) |
| `anthropic` | LLM (Claude direct) | [console.anthropic.com](https://console.anthropic.com) |
@@ -164,6 +165,36 @@ ANTHROPIC_API_KEY="$(bw get password api/anthropic)" nanobot agent
| `github_copilot` | LLM (GitHub Copilot, OAuth) | `nanobot provider login github-copilot` |
| `qianfan` | LLM (Baidu Qianfan) | [cloud.baidu.com](https://cloud.baidu.com/doc/qianfan/s/Hmh4suq26) |
+
+Skywork / APIFree
+
+Skywork uses the OpenAI-compatible APIFree API endpoint. Configure the provider
+once, then use Skywork model IDs such as `skywork-ai/skyclaw-v1`.
+
+```json
+{
+ "providers": {
+ "skywork": {
+ "apiKey": "${SKYWORK_API_KEY}",
+ "apiBase": "https://api.apifree.ai/v1"
+ }
+ },
+ "agents": {
+ "defaults": {
+ "provider": "skywork",
+ "model": "skywork-ai/skyclaw-v1",
+ "maxTokens": 32768,
+ "contextWindowTokens": 131072
+ }
+ }
+}
+```
+
+You can also reference `${APIFREE_API_KEY}` in `apiKey` if that is how your
+environment names the credential.
+
+
+
AWS Bedrock (Converse API)
diff --git a/nanobot/config/schema.py b/nanobot/config/schema.py
index 6ccabea3f..c0ad7e758 100644
--- a/nanobot/config/schema.py
+++ b/nanobot/config/schema.py
@@ -190,6 +190,7 @@ class ProvidersConfig(Base):
openai: ProviderConfig = Field(default_factory=ProviderConfig)
openrouter: ProviderConfig = Field(default_factory=ProviderConfig)
huggingface: ProviderConfig = Field(default_factory=ProviderConfig)
+ skywork: ProviderConfig = Field(default_factory=ProviderConfig) # Skywork / APIFree API gateway
deepseek: ProviderConfig = Field(default_factory=ProviderConfig)
groq: ProviderConfig = Field(default_factory=ProviderConfig)
zhipu: ProviderConfig = Field(default_factory=ProviderConfig)
diff --git a/nanobot/providers/registry.py b/nanobot/providers/registry.py
index 0f8e45936..58c2ff399 100644
--- a/nanobot/providers/registry.py
+++ b/nanobot/providers/registry.py
@@ -155,6 +155,18 @@ PROVIDERS: tuple[ProviderSpec, ...] = (
detect_by_base_keyword="huggingface",
default_api_base="https://router.huggingface.co/v1",
),
+ # Skywork API platform (APIFree): OpenAI-compatible MaaS gateway.
+ ProviderSpec(
+ name="skywork",
+ keywords=("skywork", "skyclaw", "apifree"),
+ env_key="SKYWORK_API_KEY",
+ display_name="Skywork",
+ backend="openai_compat",
+ env_extras=(("APIFREE_API_KEY", "{api_key}"),),
+ is_gateway=True,
+ detect_by_base_keyword="apifree.ai",
+ default_api_base="https://api.apifree.ai/v1",
+ ),
# AiHubMix: global gateway, OpenAI-compatible interface.
# strip_model_prefix=True: doesn't understand "anthropic/claude-3",
# strips to bare "claude-3".
diff --git a/tests/channels/test_websocket_channel.py b/tests/channels/test_websocket_channel.py
index 78953864e..d687cc9e2 100644
--- a/tests/channels/test_websocket_channel.py
+++ b/tests/channels/test_websocket_channel.py
@@ -1030,6 +1030,8 @@ async def test_settings_api_returns_safe_subset_and_updates_whitelist(
assert providers["azure_openai"]["api_key_required"] is True
assert providers["openrouter"]["configured"] is False
assert providers["openrouter"]["api_key_required"] is True
+ assert providers["skywork"]["label"] == "Skywork"
+ assert providers["skywork"]["default_api_base"] == "https://api.apifree.ai/v1"
assert providers["ant_ling"]["label"] == "Ant Ling"
assert providers["ant_ling"]["default_api_base"] == "https://api.ant-ling.com/v1"
assert providers["atomic_chat"]["configured"] is False
diff --git a/tests/providers/test_skywork_provider.py b/tests/providers/test_skywork_provider.py
new file mode 100644
index 000000000..52de462f4
--- /dev/null
+++ b/tests/providers/test_skywork_provider.py
@@ -0,0 +1,80 @@
+"""Tests for the Skywork provider registration."""
+
+from unittest.mock import patch
+
+from nanobot.config.schema import Config, ProvidersConfig
+from nanobot.providers.openai_compat_provider import OpenAICompatProvider
+from nanobot.providers.registry import PROVIDERS, find_by_name
+
+
+def test_skywork_config_field_exists() -> None:
+ config = ProvidersConfig()
+
+ assert hasattr(config, "skywork")
+
+
+def test_skywork_provider_in_registry() -> None:
+ specs = {spec.name: spec for spec in PROVIDERS}
+
+ assert "skywork" in specs
+ skywork = specs["skywork"]
+ assert skywork.backend == "openai_compat"
+ assert skywork.env_key == "SKYWORK_API_KEY"
+ assert ("APIFREE_API_KEY", "{api_key}") in skywork.env_extras
+ assert skywork.display_name == "Skywork"
+ assert skywork.is_gateway is True
+ assert skywork.detect_by_base_keyword == "apifree.ai"
+ assert skywork.default_api_base == "https://api.apifree.ai/v1"
+ assert skywork.supports_max_completion_tokens is False
+
+
+def test_find_by_name_skywork() -> None:
+ spec = find_by_name("skywork")
+
+ assert spec is not None
+ assert spec.name == "skywork"
+
+
+def test_skywork_model_auto_matches_with_default_api_base() -> None:
+ config = Config.model_validate(
+ {
+ "providers": {
+ "skywork": {
+ "apiKey": "sky-key",
+ },
+ },
+ "agents": {
+ "defaults": {
+ "model": "skywork-ai/skyclaw-v1",
+ },
+ },
+ }
+ )
+
+ assert config.get_provider_name("skywork-ai/skyclaw-v1") == "skywork"
+ assert config.get_api_key("skywork-ai/skyclaw-v1") == "sky-key"
+ assert config.get_api_base("skywork-ai/skyclaw-v1") == "https://api.apifree.ai/v1"
+
+
+def test_skywork_preserves_model_id_and_uses_chat_completion_max_tokens() -> None:
+ spec = find_by_name("skywork")
+ with patch("nanobot.providers.openai_compat_provider.AsyncOpenAI"):
+ provider = OpenAICompatProvider(
+ api_key="sky-key",
+ default_model="skywork-ai/skyclaw-v1",
+ spec=spec,
+ )
+
+ kwargs = provider._build_kwargs(
+ messages=[{"role": "user", "content": "hi"}],
+ tools=None,
+ model="skywork-ai/skyclaw-v1",
+ max_tokens=1024,
+ temperature=0.7,
+ reasoning_effort=None,
+ tool_choice=None,
+ )
+
+ assert kwargs["model"] == "skywork-ai/skyclaw-v1"
+ assert kwargs["max_tokens"] == 1024
+ assert "max_completion_tokens" not in kwargs
diff --git a/webui/src/components/settings/SettingsView.tsx b/webui/src/components/settings/SettingsView.tsx
index d4a47c1f0..249d400c8 100644
--- a/webui/src/components/settings/SettingsView.tsx
+++ b/webui/src/components/settings/SettingsView.tsx
@@ -2129,6 +2129,7 @@ function providerLabel(
const PROVIDER_ICONS: Record = {
custom: Hexagon,
openrouter: Sparkles,
+ skywork: Sparkles,
aihubmix: Triangle,
anthropic: Brain,
openai: Bot,