
Run a compatible AI API on your own machine, verify it locally, and make it available to authorized remote clients
WindsurfAPI is a self-hosted Node.js HTTP service that presents OpenAI-compatible, Anthropic-compatible, and Gemini-compatible routes for clients such as Claude Code and Cline. In this guide, we install the service from its repository, configure authentication, add an authorized account, start it on port 3003, and test the API before introducing any public access. Once the local service works, we create an HTTP tunnel with Localtonet so an approved remote client can reach it without inbound router port forwarding, firewall changes, a VPN, or a public IP address. We also cover Docker, routine operations, security boundaries, and troubleshooting.
๐ What's in this guide
What WindsurfAPI does in this workflow
WindsurfAPI runs as an HTTP service on the host you control. Its documented default service port is 3003. Instead of requiring every client to understand the project's upstream communication mechanism, it exposes familiar API shapes that existing development tools and SDKs can call.
The supported routes documented by the project include POST /v1/chat/completions for OpenAI-compatible chat requests, POST /v1/responses for the OpenAI Responses format, POST /v1/messages for Anthropic-compatible clients, and routes below /v1beta/models/ for Gemini-compatible requests. It also documents the legacy POST /v1/completions route. Available models depend on the account connected to the service, so this guide deliberately does not hardcode a model name.
Claude Code and Cline are clients in this design. WindsurfAPI passes model messages, tool requests, and tool results between the client and its configured upstream account. File reading, editing, command execution, and similar agent actions occur in the client environment, not inside the WindsurfAPI HTTP server. This distinction matters when deciding where to run each component and what permissions to grant.
/dashboard, with authentication controlled separately from API client authentication.
Use only an account that you own or are explicitly authorized to operate. Review the current WindsurfAPI license, repository notices, and the applicable upstream service terms before using the project, especially for commercial use, shared hosting, resale, or a publicly offered relay. A working technical configuration does not itself establish permission for a particular use.
Prerequisites and deployment choices
The most direct documented installation path uses Git, Bash, and Node.js. The project's current deployment documentation specifies Node.js 20 or newer. You also need outbound network access from the host so it can clone the repository and communicate with the upstream service selected by your configuration.
For the script-based installation in this guide, prepare:
- A machine on which you are authorized to install and run software.
- Node.js 20 or newer.
- Git for cloning and updating the repository.
- Bash for
setup.shand the project's maintenance scripts. - An authorized Windsurf or Devin account compatible with the path you intend to use.
- Permission to use port
3003, or a deliberate alternative configured through the project's documentedPORTsetting. - A Localtonet account and client installation for the later remote-access stage.
Docker users need Docker Engine with the Compose plugin instead of a host-level Node.js runtime. The supplied Compose configuration builds the application image and defines persistent mounts. The project documentation states that Compose defaults to the direct Devin connection path through DEVIN_CONNECT=1. In that mode, it does not run or automatically download the optional Language Server. If you intentionally disable that path and select the traditional Cascade route, Language Server installation and platform compatibility become separate requirements.
| Installation path | Best suited to | Important behavior |
|---|---|---|
setup.sh and Node.js |
A straightforward host installation | The setup script creates required directories, sets permissions, and generates the environment file before direct startup. |
| Docker Compose | Container-based operation and repeatable deployment | The documented Compose workflow builds the image and persists data beneath .docker-data by default. |
| Manual repository installation | Operators who need explicit control over the optional Language Server path | The project provides install-ls.sh for supported Linux and macOS architectures, but this is not required by the documented Compose default using DEVIN_CONNECT=1. |
| Windows release package | Windows users who prefer a packaged executable | The project publishes Windows release assets, but exact package behavior can change between releases. Check the selected release instructions before use. |
WindsurfAPI is actively developed, and its documentation describes both a direct Devin connection path and a traditional Language Server path. Read the generated .env comments and release notes for the exact version you clone. Do not copy an old configuration over a newer installation without checking whether its defaults or authentication behavior changed.
Install and start WindsurfAPI
The repository's quick-start workflow is the preferred starting point here because it prepares the host installation and generates the initial environment file. Run the commands from a shell on the machine that will host the service.
Clone the official repository
Clone the project and enter its directory. Confirm that the remote URL points to the intended repository before running any included script.
git clone https://github.com/dwgx/WindsurfAPI.git
cd WindsurfAPI
Run the documented setup script
The project describes this script as creating directories, applying permissions, and generating .env. Review scripts before execution when operating in a controlled or production environment.
bash setup.sh
Review the generated configuration
Open .env locally and inspect the current comments and generated values. Pay particular attention to PORT, API_KEY, DASHBOARD_PASSWORD, and the connection mode. Never paste the file into a ticket, chat, article, or source repository.
Start the Node.js service
Start the documented entry point. Keep this terminal open during initial verification so startup and request errors remain visible.
node src/index.js
Open the dashboard locally
On the same machine, browse to http://localhost:3003/dashboard. If you changed PORT, use that configured value instead. Authenticate with the dashboard password established by your installation.
If you prefer Docker Compose, copy the example environment file, review it, and then build and start the service:
git clone https://github.com/dwgx/WindsurfAPI.git
cd WindsurfAPI
cp .env.example .env
docker compose up -d --build
docker compose logs -f
The documented default mounts include ./.docker-data/data for account, proxy, statistics, runtime, model-access, and log data; ./.docker-data/opt/windsurf for optional Language Server files and data; and ./.docker-data/tmp/windsurf-workspace for the temporary workspace. The Docker configuration uses /data as its default internal data directory. The project documents DATA_DIR for changing that location, but adjust storage only after understanding the Compose mounts and backup implications.
The current project documentation describes the Compose setup as binding broadly while failing closed when API_KEY or DASHBOARD_PASSWORD is empty. Treat that behavior as version-specific protection, not a substitute for configuration. Establish strong credentials, verify that unauthorized requests are rejected, and only then proceed to remote access.
Configure authentication and add an authorized account

WindsurfAPI has two distinct access surfaces. The API is used by Claude Code, Cline, SDKs, and other clients. The dashboard is an administrative surface used to manage accounts and service settings. Protecting only the API while leaving the dashboard open would still expose sensitive operational controls, so configure and test both.
Protect the API
The project uses API_KEY for client authentication. Store a strong, unique value in .env or through the secret-management mechanism used by your deployment environment. A client using the Anthropic-compatible route supplies that key through x-api-key. OpenAI-compatible clients normally supply it as a bearer token.
Do not use a real upstream account token as the API key. These credentials serve different purposes. The API key authorizes a client to call your local WindsurfAPI service, while the account credential authorizes WindsurfAPI to use the account you have added.
Protect the dashboard
Set and retain a strong DASHBOARD_PASSWORD. The dashboard is available below /dashboard, and the project also documents an authentication route at /auth/login. Use the dashboard password only through the dashboard login flow. Do not place it in Claude Code, Cline, or an API request.
Add your authorized account
Open the local dashboard, authenticate, and use the account-management or login function presented by the installed version. Supply credentials or a session token only for an account that you own or have explicit authorization to operate. The interface and accepted login methods can change between project versions, so follow the labels shown by your running dashboard rather than assuming fields from an older screenshot.
After adding the account, confirm that it appears healthy and that the service can discover models available to that account. Model access varies by account tier. If a particular model is absent or rejected, do not assume that the tunnel, API route, or client is broken. First verify that the connected account is entitled to use that model.
Keep .env, account data, API keys, dashboard passwords, cookies, session tokens, and persistent Docker data out of Git. Restrict filesystem access to the service account and administrators who need it. Redact secrets before sharing logs. If a credential is exposed, rotate it at the system that issued it and update the deployment.
Verify WindsurfAPI locally before creating a tunnel
Remote access should never be the first test. A Localtonet HTTP tunnel forwards traffic to the local target you configure, but it cannot repair an application that has not started, is listening on a different port, rejects its account, or has invalid credentials. Verify each layer locally so later failures have a clear boundary.
Confirm that the process remains running
For direct Node.js startup, inspect the terminal for immediate exceptions. For Docker, inspect docker compose logs -f and confirm that the container does not repeatedly restart.
Load the local dashboard
Open http://localhost:3003/dashboard. A rendered login or dashboard response confirms basic HTTP reachability. It does not yet prove that an account or model request works.
Check authenticated model discovery
Query the documented model catalog using an API key stored in a shell variable. The key below is a placeholder and must not be copied as a real credential.
export WINDSURFAPI_KEY='REPLACE_WITH_YOUR_LOCAL_API_KEY'
curl \
-H "Authorization: Bearer ${WINDSURFAPI_KEY}" \
http://localhost:3003/v1/models
Send a minimal Anthropic-compatible request
Select a model identifier actually returned for your account. A successful response verifies the HTTP route, API authentication, account configuration, model entitlement, and upstream communication.
curl http://localhost:3003/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: ${WINDSURFAPI_KEY}" \
-d '{
"model": "REPLACE_WITH_AN_AVAILABLE_MODEL",
"max_tokens": 64,
"messages": [
{
"role": "user",
"content": "Reply with the word ready."
}
]
}'
Test the unauthorized case
Repeat a request without the API key or with an intentionally invalid value. It should not produce an authorized model response. Also verify that the dashboard cannot be administered without its password.
Interpret errors by layer. A connection refusal usually means no process is listening at the specified host and port. An HTTP authentication error indicates that the service is reachable but the API key or dashboard credentials are missing or incorrect. A model or account error after successful authentication points toward account health, entitlement, rate limits, or upstream communication rather than local networking.
Connect Claude Code and Cline locally

Once the direct API request works, configure your client against the local endpoint. Start locally even if the final goal is remote access. This separates client configuration errors from tunnel configuration errors.
Claude Code
WindsurfAPI documents the Anthropic-compatible base URL for Claude Code. Set the base URL to the server origin, not to /v1/messages, because the client appends the appropriate API path. Set the API key to the value configured for WindsurfAPI.
export ANTHROPIC_BASE_URL="http://localhost:3003"
export ANTHROPIC_API_KEY="REPLACE_WITH_YOUR_LOCAL_API_KEY"
claude
These variables configure the API endpoint used by that client process. They are not the same as Anthropic's separate self-hosted cloud-session environments. This tutorial concerns connecting a locally or remotely running Claude Code client to WindsurfAPI's Anthropic-compatible HTTP endpoint.
Cline
Cline can use an OpenAI-compatible provider configuration for this workflow. Configure its base URL as:
http://localhost:3003/v1
Supply the WindsurfAPI API key and choose a model identifier confirmed by your authenticated /v1/models response. Cline versions and provider menus can change, so use the current OpenAI-compatible provider entry rather than relying on an old menu location.
If the client supports an Anthropic-compatible provider, the project also documents /v1/messages. Choose one protocol and configure its corresponding base URL convention carefully. OpenAI-compatible clients generally expect the base URL to include /v1, while Claude Code's documented ANTHROPIC_BASE_URL value is the server origin.
If Claude Code or Cline asks to read, edit, or execute something, that action is performed where the client runs and under that client's local permissions. Restrict the client workspace, review requested actions, and avoid assuming that API authentication limits filesystem access on the client machine.
Expose the verified HTTP service through Localtonet

At this point, WindsurfAPI should already respond correctly at http://localhost:3003. The next step is connectivity, not installation. With Localtonet, our client application establishes an outbound connection to a Localtonet relay server. The resulting HTTP tunnel provides a public HTTPS address without requiring inbound router port forwarding, firewall changes, VPN setup, or a public IP address.
Run the Localtonet client on the WindsurfAPI host or on another device that can reach the host and port. If both applications run on the same machine, 127.0.0.1 and port 3003 are the clearest local target. If the client runs on a separate device, the WindsurfAPI service must be reachable from that device through an authorized private network address. Do not change the application to listen on every interface unless that is necessary and protected by your network controls.
Install and run the Localtonet client
Install our client application on the device that can reach WindsurfAPI. Keep the client running because the tunnel is available only while the selected device is connected and the tunnel is running.
Authenticate the correct device
Select the device using its Localtonet auth token. Tokens are device-specific secrets. Obtain the actual value from your account and never paste it into documentation, code, screenshots, or support messages.
Select an available relay server
Choose a server or region currently available in your dashboard. Availability can vary, so this guide does not hardcode a server code or claim that every option is included with every plan.
Create an HTTP tunnel to WindsurfAPI
Configure an HTTP tunnel with local IP 127.0.0.1 and local port 3003 when both services run on the same host. HTTP process types can use a random subdomain, a supported custom subdomain, or a custom domain. Exact availability and any custom-domain DNS requirements must be checked in the current dashboard and documentation.
Start the tunnel
Creating a tunnel does not start it. Use the Start control and wait until the tunnel is running. Copy the assigned public HTTPS URL from the dashboard.
Test the public endpoint and stop it when finished
Repeat the authenticated model or message test using the assigned HTTPS origin. After testing, configure authorized remote clients with that origin. Stop or delete the tunnel when remote access is no longer needed.
For example, if the assigned address were represented by the placeholder https://YOUR-ASSIGNED-HOST, Claude Code would use:
export ANTHROPIC_BASE_URL="https://YOUR-ASSIGNED-HOST"
export ANTHROPIC_API_KEY="REPLACE_WITH_YOUR_WINDSURFAPI_KEY"
claude
Cline's OpenAI-compatible base URL would use the same assigned origin with /v1 appended:
https://YOUR-ASSIGNED-HOST/v1
These are placeholders, not real Localtonet addresses. Always copy the assigned URL from your running tunnel. You can review current tunnel categories and controls in the Localtonet documentation.
Anyone who learns the URL can attempt to reach the service. Keep the WindsurfAPI API key and dashboard password enabled, use distinct strong credentials, and share the endpoint only with authorized users. Do not put the API key in a public repository or embed it in client-side web code. A tunnel provides connectivity, not authorization for the application behind it.
Routine operations, updates, and troubleshooting
Keep the service available deliberately
A foreground node src/index.js process is useful during installation because logs remain visible, but it stops when its terminal ends. The project documents PM2 as an optional process manager and provides an update script that can interact with PM2. If you adopt a process manager, configure it according to your operating system and validate restart behavior before relying on unattended access.
Docker operators can inspect current state and logs with:
docker compose ps
docker compose logs -f
Preserve the mounted data directories when rebuilding or replacing the container. Backups containing account information and configuration are sensitive and need the same access controls as the live data.
Update with a recovery plan
The repository provides update.sh. Its documented workflow pulls changes, updates the optional Language Server through install-ls.sh, stops PM2, clears a residual process on port 3003, restarts the application, and performs a health check. Because this can change both application code and runtime state, inspect the script and release notes before running it.
cd WindsurfAPI
bash update.sh
Back up the environment file and persistent data first, while protecting those backups as secrets. After an update, repeat local dashboard access, authenticated model discovery, a minimal message request, and the unauthorized-request test. Only then restart or continue using the Localtonet tunnel.
Diagnose common failures by layer
| Symptom | Likely layer | What to verify |
|---|---|---|
Connection refused at localhost:3003 |
Local process or port | Confirm that Node.js or the container is running, inspect logs, and verify the configured PORT. |
| Local URL works, but the public URL does not | Localtonet client or tunnel | Confirm that the correct device is connected, the HTTP target is correct, and the created tunnel has actually been started. |
| Public response is 401 or another authentication error | WindsurfAPI authentication | Verify the client header and API key. Reachability is working if the application returns an HTTP authentication response. |
| Dashboard opens but account requests fail | Account or upstream connection | Check account health, authorization, connection mode, upstream access, and redacted server logs. |
| A selected model is unavailable | Model entitlement or identifier | Query /v1/models with valid authentication and choose an identifier actually available to the connected account. |
| Claude Code calls the wrong URL | Client base URL | Use the server origin for ANTHROPIC_BASE_URL. Do not append /v1/messages manually. |
| Cline receives route-not-found errors | Provider or base-path configuration | For its OpenAI-compatible provider, use the origin followed by /v1 and select an available model. |
| The tunnel disappears after a restart | Tunnel lifecycle | Confirm that the Localtonet client reconnected and that the tunnel is running. A configured tunnel is not automatically equivalent to a running tunnel. |
Reduce the exposed surface
If remote clients need only API access, avoid routinely opening the dashboard through a browser over the public address. The HTTP tunnel forwards paths for the same application origin, so application authentication remains the control protecting administrative routes. Use strong dashboard credentials, access administration only when needed, and stop the tunnel when remote API access is no longer required.
Apply least privilege to the host as well. Run WindsurfAPI under a non-administrative account where practical, restrict access to its data directory, limit which users can read process environments, and keep the agent client's workspace separate from unrelated files. If the host is shared, remember that users with sufficient process or filesystem access may be able to obtain application secrets regardless of network controls.
Frequently asked questions
Does WindsurfAPI require Node.js?
The direct repository workflow requires Node.js 20 or newer. Docker Compose packages the runtime into the container, while the project's Windows release assets provide a separate packaged route. Exact release packaging can change, so review the instructions attached to the version you download.
Which port does WindsurfAPI use?
The documented service port is 3003. The project also documents a PORT configuration setting. If you change it, update local tests, client base URLs, and the Localtonet HTTP tunnel target to use the same value.
Should the Localtonet tunnel be created before testing WindsurfAPI?
No. First verify the dashboard, authenticated model list, a minimal API request, and rejection of unauthorized requests through localhost. Add the HTTP tunnel only after the local application works. This makes it much easier to distinguish application problems from connectivity problems.
Does Localtonet remove the need for an API key?
No. Our HTTP tunnel supplies connectivity from a public HTTPS address to the local target. WindsurfAPI must still authenticate API clients and protect its dashboard. Keep both API_KEY and DASHBOARD_PASSWORD configured with strong, distinct values.
What Localtonet target should I use when both applications run on the same machine?
Use an HTTP tunnel targeting local IP 127.0.0.1 and port 3003, unless you deliberately changed the WindsurfAPI port. If the Localtonet client runs on another device, use an authorized private address that the client device can reach.
Why does a model listed in an example fail for my account?
Model availability depends on the connected account and can change. Authenticate to /v1/models and use an identifier returned for your account instead of copying a model name from an old example.
Is this the same as Claude Code self-hosted cloud environments?
No. This guide configures Claude Code as an API client using an Anthropic-compatible base URL. Claude Code self-hosted cloud environments concern where cloud sessions execute and have separate organizational, runner, plan, and networking requirements.
Does the tunnel stay available if the host shuts down?
No. The public endpoint depends on the selected Localtonet client device being connected and the tunnel running. WindsurfAPI must also remain running and reachable at the configured local target.
Connect your verified WindsurfAPI service with Localtonet
After WindsurfAPI responds locally and rejects unauthorized requests, create an HTTP tunnel to port 3003 and give approved Claude Code or Cline clients a public HTTPS base URL without opening an inbound router port.
Get Started Free โ