API Reference
Last updated: 23 September 2026
Goalfinder is an OpenAI-compatible gateway. Point any OpenAI SDK at our base URL, use your Goalfinder key, and call every model in the catalog through one endpoint.
Quickstart
Create an API key in your dashboard, then make your first request. New accounts start with $1 of free credit — no card required.
Using OpenCode, Cline, or Roo Code? Start at /docs/clients for copy-paste configs.
curl https://www.goalfinder.space/api/v1/chat/completions \
-H "Authorization: Bearer $GOALFINDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4.1-flash",
"messages": [{ "role": "user", "content": "Hello!" }]
}'Authentication
Authenticate with a bearer token. Every request must carry your key; keys are shown once at creation and stored only as one-way hashes, so we cannot recover a lost key — revoke it and issue a new one.
Authorization: Bearer gf-sk-...
Requests without a valid key return 401. Keep keys server-side: never ship one in a browser bundle, mobile app or public repository.
Endpoints
All endpoints are relative to https://www.goalfinder.space/api/v1.
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/chat/completions | Chat completion, streaming or non-streaming. |
| GET | /api/v1/models | List active models with their per-1M-token pricing. |
base_url to https://www.goalfinder.space/api/v1. Note that the base URL already includes the /api/v1 prefix — do not append /v1 again.Python
The official OpenAI Python SDK works unmodified. Install it with pip install openai.
from openai import OpenAI
client = OpenAI(
base_url="https://www.goalfinder.space/api/v1",
api_key="gf-sk-...",
)
resp = client.chat.completions.create(
model="deepseek-v4.1-flash",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain caching in one sentence."},
],
)
print(resp.choices[0].message.content)
print(resp.usage.total_tokens)Node.js
The same applies to the official openai npm package.
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://www.goalfinder.space/api/v1",
apiKey: process.env.GOALFINDER_API_KEY,
});
const resp = await client.chat.completions.create({
model: "deepseek-v4.1-flash",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Explain caching in one sentence." },
],
});
console.log(resp.choices[0].message.content);
console.log(resp.usage?.total_tokens);Streaming
Pass stream: true to receive server-sent events. Each chunk carries choices[0].delta.content, and the stream ends with the literal terminator data: [DONE].
stream = client.chat.completions.create(
model="deepseek-v4.1-flash",
messages=[{"role": "user", "content": "Count to five."}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)const stream = await client.chat.completions.create({
model: "deepseek-v4.1-flash",
messages: [{ role: "user", content: "Count to five." }],
stream: true,
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content;
if (delta) process.stdout.write(delta);
}Usage is settled when the stream finishes. If a client disconnects early, we settle for the tokens already delivered rather than the whole request.
Tool calling
Function/tool calling passes straight through to the upstream model. Declare tools in the standard OpenAI shape and the model will return tool_calls that you execute and feed back as a role: "tool" message.
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
resp = client.chat.completions.create(
model="deepseek-v4.1-flash",
messages=[{"role": "user", "content": "Weather in Toronto?"}],
tools=tools,
tool_choice="auto",
)
call = resp.choices[0].message.tool_calls[0]
print(call.function.name, call.function.arguments) # get_weather {"city":"Toronto"}
# Run your function, then send the result back.
resp2 = client.chat.completions.create(
model="deepseek-v4.1-flash",
messages=[
{"role": "user", "content": "Weather in Toronto?"},
resp.choices[0].message,
{"role": "tool", "tool_call_id": call.id, "content": "18C, clear"},
],
tools=tools,
)
print(resp2.choices[0].message.content)const tools = [{
type: "function" as const,
function: {
name: "get_weather",
description: "Get the current weather for a city.",
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
},
}];
const resp = await client.chat.completions.create({
model: "deepseek-v4.1-flash",
messages: [{ role: "user", content: "Weather in Toronto?" }],
tools,
tool_choice: "auto",
});
const call = resp.choices[0].message.tool_calls![0];
console.log(call.function.name, call.function.arguments);tool_calls rather than content. If you are reconciling your balance against usage, prefer the usage object on the response over counting characters. Long tool-call loops can consume credit quickly — keep tool_choice and iteration caps tight.Error codes
Errors use the OpenAI envelope. Check error.type for programmatic handling and error.code for the specific case.
{
"error": {
"message": "Invalid or revoked API key.",
"type": "authentication_error",
"code": "invalid_api_key"
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request_error | Malformed JSON body or missing a required field such as model. |
| 401 | missing_api_key | No Authorization header was sent. |
| 401 | invalid_api_key | The key is wrong or has been revoked. |
| 402 | insufficient_balance | Your balance will not cover the worst-case cost of this request. Buy a pack to continue. |
| 404 | model_not_found | The model does not exist or is not active. Check GET /api/v1/models. |
| 429 | rate_limit_exceeded | Too many requests. No RPM quota is published — see Rate limits below. Back off and retry. |
| 500 | configuration_error | A required upstream endpoint is not configured. Contact support. |
| 502 | upstream_error | The model provider failed or was unreachable. Retry; you are not billed for a request that never ran. |
| 503 | upstream_error | The provider is overloaded with no available channel. Retry with backoff. |
A 402 is returned before any upstream call is made, so a rejected request never costs you anything. Retry 429 and 5xx responses with exponential backoff and jitter.
Not in v1
This gateway implements the OpenAI-compatible Chat Completions surface, and nothing else. Being explicit about what is absent is deliberate — so you can design around it rather than discover it in production. Everything below returns 404 (no such route) rather than 501.
| Not supported | Endpoint | Notes |
|---|---|---|
| Embeddings | POST /v1/embeddings | No vector endpoints at all. Use a dedicated embedding provider, or have the model return vectors as text via response_format. |
| Image generation | POST /v1/images/generations | We do not generate images. |
| Image input (vision) | POST /v1/chat/completions | The catalog is text-only. Message content must be a string — an array of content blocks is rejected. Do not send base64 image parts. |
| Anthropic Messages | POST /v1/messages | Not exposed. Everything goes through Chat Completions, including the Qwen models. |
| OpenAI Responses API | POST /v1/responses | Not exposed. Use Chat Completions; there is no stateful conversation object. |
| Web search | web_search_options | Not supported. The upstream may reject the parameter rather than ignore it. |
| Extended thinking | thinking | Not configurable. Reasoning tokens are not billed separately, and are not returned as a distinct field. |
| Assistants, Batches, Files, Fine-tuning, Audio | various | Not implemented. |
Tool calling is supported. The tools and tool_choice parameters are passed through, function calls are returned normally, and streaming emits delta.tool_calls incrementally as you would expect. A response that is only a tool call is billed normally — the payload counts toward completion tokens exactly like assistant text.
Measurement comes from the upstream usage report whenever the provider sends one, which is the normal case. If a provider omits it, we fall back to estimating completion tokens from the returned payload, including tool-call arguments. That fallback is approximate by nature, so if you ever see a token count that looks off against your own accounting, tell us and we will reconcile it.
Rate limits
There is no fixed request-per-minute quota published or enforced at the gateway today. Throughput is bounded by two things instead:
- Your balance. Each request is pre-authorised against its worst-case cost, and a request we cannot afford returns
402. - Upstream capacity. Our providers impose their own limits, which they do not publish and which we cannot increase. Under load you may see
429or503passed through from upstream.
Because no RPM is published, do not assume one. Handle 429 and 503 with exponential backoff and jitter, and treat concurrency as best-effort. If you need a committed throughput number for a production workload, contact us before you build on the assumption.
Model matrix
The catalog below is read live from production. Prices are per 1M tokens, in US dollars, at the rates you are billed.
The catalog is unavailable right now. Call GET /api/v1/models for the live list.
Chat completions parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| model | string | Yes | ID of the model to use. |
| messages | array | Yes | Conversation history. Roles: system, user, assistant, tool. |
| stream | boolean | No | Send partial deltas as server-sent events. Default false. |
| max_tokens | integer | No | Maximum tokens to generate. Also caps the worst-case cost we pre-authorise. |
| temperature | number | No | Sampling temperature, 0 to 2. Default 1. |
| top_p | number | No | Nucleus sampling mass. Default 1. |
| stop | string | string[] | No | Up to 4 sequences that halt generation. |
| n | integer | No | How many choices to generate. Default 1. |
| frequency_penalty | number | No | -2.0 to 2.0. Penalise repeated tokens. Default 0. |
| presence_penalty | number | No | -2.0 to 2.0. Penalise tokens seen so far. Default 0. |
| seed | integer | No | Deterministic sampling, where the model supports it. |
| response_format | object | No | { type: "text" } or { type: "json_object" }. |
| tools | array | No | Tools the model may call. See Tool calling. |
| tool_choice | string | object | No | "auto", "none", "required", or a specific function. |
| user | string | No | Opaque end-user identifier for abuse monitoring. |
How billing works
- Usage is metered per token against your prepaid balance, using the per-model rates in the matrix above.
- Before calling upstream we compute the worst-case cost of the request from your prompt size and
max_tokens. If your balance will not cover it, the request is rejected with a 402 and nothing is sent upstream. - We settle the actual cost once the response completes, from the usage the provider reports.
- Cached input tokens are billed at the lower cache-read rate shown in the matrix, which makes repeated prompts with a stable prefix cheaper.
- New accounts receive $1 of free credit to try the API. It is a one-time promotional grant per person and is not a purchase.