PaceBar
Providers

Windsurf

Windsurf — prompt credits, flex credits.

Reverse-engineered from app bundle, extension.js, and language server binary. May change without notice.

Windsurf and Antigravity share the same Codeium language server binary and Connect-RPC protocol. The discovery, port probing, and RPC endpoints are virtually identical — the key differences are authentication (Windsurf requires an API key) and the usage model (credits vs fractions).

Variants

The plugin supports two Windsurf variants, probed in this order:

VariantAppBundle ID--ide_nameApp Support dir
WindsurfWindsurf.appcom.exafunction.windsurfwindsurf~/Library/Application Support/Windsurf/
Windsurf NextWindsurf - Next.appcom.exafunction.windsurfNextwindsurf-next~/Library/Application Support/Windsurf - Next/

Both use the same Codeium language server binary (language_server_macos_arm), same Connect-RPC service, same CSRF auth, and same GetUserStatus RPC. They differ only in:

  • Process marker: windsurf vs windsurf-next (matched via --ide_name exact value)
  • SQLite path: Windsurf/User/globalStorage/state.vscdb vs Windsurf - Next/User/globalStorage/state.vscdb
  • ideName in metadata: windsurf vs windsurf-next

Overview

  • Vendor: Codeium (Windsurf)
  • Protocol: Connect RPC v1 (JSON over HTTP) on local language server
  • Service: exa.language_server_pb.LanguageServerService
  • Auth: API key (sk-ws-01-...) from SQLite + CSRF token from process args
  • Usage model: credit-based (prompt + flex credits, stored in hundredths)
  • Billing cycle: monthly (planStart / planEnd)
  • Timestamps: ISO 8601
  • Requires: Windsurf IDE running (language server is a child process), or signed-in credentials in SQLite (cloud fallback)

Discovery

Same process as Antigravity — same binary, same flags. The distinguishing marker is the --ide_name flag value in the process args.

# 1. Find process and extract CSRF token + version
ps -ax -o pid=,command= | grep 'language_server_macos'
# Windsurf:      --ide_name windsurf
# Windsurf Next: --ide_name windsurf-next
# Extract: --csrf_token <token>
# Extract: --windsurf_version <version>
# Extract: --extension_server_port <port>  (HTTP fallback)

# 2. Find listening ports
lsof -nP -iTCP -sTCP:LISTEN -a -p <pid>

# 3. Probe each port to find the Connect-RPC endpoint
POST https://127.0.0.1:<port>/.../GetUnleashData any response wins

# 4. Get API key from SQLite (path depends on variant)
# Windsurf:
sqlite3 ~/Library/Application\ Support/Windsurf/User/globalStorage/state.vscdb \
  "SELECT value FROM ItemTable WHERE key = 'windsurfAuthStatus'"
# Windsurf Next:
sqlite3 ~/Library/Application\ Support/Windsurf\ -\ Next/User/globalStorage/state.vscdb \
  "SELECT value FROM ItemTable WHERE key = 'windsurfAuthStatus'"
# → JSON: { apiKey: "sk-ws-01-...", ... }

Port and CSRF token change on every IDE restart. The LS may use HTTPS with a self-signed cert.

Headers (all local requests)

HeaderRequiredValue
Content-Typeyesapplication/json
Connect-Protocol-Versionyes1
x-codeium-csrf-tokenyes<csrf_token> (from process args)

Endpoints

GetUserStatus (primary)

Returns plan info with credit-based usage for the current billing cycle. Same RPC as Antigravity, but requires metadata.apiKey.

POST http://127.0.0.1:{port}/exa.language_server_pb.LanguageServerService/GetUserStatus

Request

{
  "metadata": {
    "apiKey": "sk-ws-01-...",
    "ideName": "windsurf",
    "ideVersion": "<version>",
    "extensionName": "windsurf",
    "extensionVersion": "<version>",
    "locale": "en"
  }
}

For Windsurf Next, use "ideName": "windsurf-next" and "extensionName": "windsurf-next".

Unlike Antigravity, Windsurf requires metadata.apiKey. The LS uses it to authenticate with the cloud backend internally. Antigravity authenticates via the Google OAuth session instead.

Response (~167KB, mostly model configs)

{
  "userStatus": {
    "planStatus": {
      "planInfo": {
        "planName": "Teams",                    // "Free" | "Pro" | "Teams" | "Free Trial"
        "monthlyPromptCredits": 50000,
        "monthlyFlexCreditPurchaseAmount": 100000
      },
      "planStart": "2026-01-18T09:07:17Z",
      "planEnd": "2026-02-18T09:07:17Z",
      "availablePromptCredits": 50000,          // total pool (in hundredths)
      "usedPromptCredits": 4700,                // consumed (omitted when 0)
      "availableFlexCredits": 2679300,
      "usedFlexCredits": 175550
    }
  }
}

Field mapping

Response fieldDisplayNotes
availablePromptCreditsTotal credit poolDivide by 100 for display (50000 → 500)
usedPromptCreditsCredits consumedDivide by 100
planStart / planEndBilling cycleISO 8601, used for pacing
negative available*UnlimitedSkip that credit line

Differences from Antigravity

WindsurfAntigravity
AuthAPI key (sk-ws-01-) required in metadataNo API key needed (CSRF only)
Usage modelCredit-based (prompt + flex)Fraction-based per model (0.0–1.0)
Credit unitsStored in hundredths (÷100 for display)N/A
Billing cycleMonthly (planStart/planEnd)5-hour rolling windows per model
Version flag--windsurf_versionN/A
Token locationSQLite windsurfAuthStatusapiKeyNot needed
Models shownNot used (credits are the metric)Per-model quota bars

Both use the same Codeium language server binary, same Connect-RPC service, same CSRF auth, same discovery process (ps + lsof + port probe), and same GetUserStatus RPC.

Token Location

SQLite database — path depends on variant:

VariantPath
Windsurf~/Library/Application Support/Windsurf/User/globalStorage/state.vscdb
Windsurf Next~/Library/Application Support/Windsurf - Next/User/globalStorage/state.vscdb
KeyValue
windsurfAuthStatusJSON: { apiKey: "sk-ws-01-...", ... }

Cloud API (fallback)

When the language server is not running, the plugin falls back to Codeium's cloud API using the API key from SQLite.

GetUserStatus (cloud)

POST https://server.codeium.com/exa.seat_management_pb.SeatManagementService/GetUserStatus

Headers

HeaderRequiredValue
Content-Typeyesapplication/json
Connect-Protocol-Versionyes1

Request

{
  "metadata": {
    "apiKey": "sk-ws-01-...",
    "ideName": "windsurf",
    "ideVersion": "0.0.0",
    "extensionName": "windsurf",
    "extensionVersion": "0.0.0",
    "locale": "en"
  }
}

Same metadata shape as the local LS endpoint. Required fields: apiKey, ideName, ideVersion, extensionName, and extensionVersion. Use semver versions (for example "0.0.0"). No CSRF token is required for cloud requests.

Response

Same userStatus.planStatus shape as the local LS response — credit fields in hundredths.

Cloud fallback uses the same API key from SQLite (windsurfAuthStatus).

Plugin Strategy

  1. Try each variant in order: Windsurf → Windsurf Next
  2. Discover LS process via ctx.host.ls.discover() (ps + lsof) with variant-specific marker
  3. Read API key from SQLite (windsurfAuthStatus) at variant-specific path
  4. Probe ports with GetUnleashData to find the Connect-RPC endpoint
  5. Call local GetUserStatus with apiKey and variant-specific ideName in metadata
  6. Build prompt + flex credit lines with billing cycle pacing (÷100 for display)
  7. If LS probe fails for all variants, call cloud GetUserStatus at server.codeium.com with API key in metadata
  8. If both local and cloud paths fail: error "Start Windsurf or sign in and try again."

On this page