Integration
REST API
Plain HTTP using the OpenAI Chat Completions shape. Server integrations use Chat Completions and read-only model and appearance endpoints. Hosted-widget session endpoints are managed by the widget.
Base URL and authentication
https://api.wicarax.appThis is the intended production API origin. For local development, use your configured local API origin, commonly http://localhost:4000.
Every request carries the agent key as a bearer token. The key identifies the agent, so no request ever names one.
Authorization: Bearer wcx_sk_YOUR_SECRET_KEY| Key | Where it may be used | Origin requirement |
|---|---|---|
wcx_sk_ | Servers, scripts, automations | None. Never expose it in a browser |
wcx_pk_ | Browsers, for public reads and widget bootstrap. It cannot call chat completions | Required. The request must come from an origin you allowed, so a publishable key will not work from a terminal |
Core integration endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /v1/chat/completions | Ask a question and receive the answer. See Chat Completions |
GET | /v1/models | Read the public model id. See Models |
GET | /v1/agent/config | Read the agent display name and cosmetic settings, for theming your own interface. Read only |
GET | /v1/widget.js | The hosted widget loader. No authentication, since the embedding page supplies the key |
The hosted widget also uses session and message endpoints internally. The table is the core integration surface, not an exhaustive count of public routes. Agents, material, keys and plans are managed in the dashboard.
Your first request
curl https://api.wicarax.app/v1/chat/completions \
-H "Authorization: Bearer $WICARAX_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "wicarax-zenith",
"messages": [
{ "role": "user", "content": "What is your refund policy?" }
]
}'The response is a complete JSON body:
{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1769500000,
"model": "wicarax-zenith",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Example answer based on your sources."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 1254,
"completion_tokens": 20,
"total_tokens": 1274
}
}Using an OpenAI client
The request and response shapes follow the OpenAI chat completions format, so libraries and tools that already speak it need two changes: the base URL and the key. What they must not do is send parameters this API has not implemented: those are refused, not ignored, so a client configured with its usual defaults will get a 400 until they are removed.
import OpenAI from 'openai';
const client = new OpenAI({
apiKey: process.env.WICARAX_SECRET_KEY,
baseURL: 'https://api.wicarax.app/v1',
});
const completion = await client.chat.completions.create({
model: 'wicarax-zenith',
messages: [{ role: 'user', content: 'What is your refund policy?' }],
});- The
modelfield is required and must be exactlywicarax-zenith. Anything else, including omitting it, is refused with400 model_not_found. It does not select a model, because there is only one; it is a contract check that stops a client believing it chose something. - Default parameters are the common first failure. An OpenAI client or framework that sends
temperature,max_tokens,stoportoolsby default will be refused with400 unsupported_parameter, andparamwill name the offending field. - Streaming is not supported. Sending
stream: trueis refused with400and the codestreaming_not_supported, rather than being silently ignored, so a client cannot believe it opted in and then mis-parse the body. systemmessages are refused, not dropped. They return400 unsupported_parameter, because silently discarding a system prompt lets a caller believe they steered the assistant. Behaviour comes from the dashboard, not from the request.
Limits to design around
| Limit | Value | What happens when exceeded |
|---|---|---|
| Request body size | About 100 KB | 413 before the request is processed |
| Requests per minute across the account | Set by the plan, applied per account and shared by every agent you own | 429 with a Retry-After header |
| Requests per month | Set by the plan, applied to the account | The call still succeeds, and the answer is a short unavailability message with zero usage |
| Failed credential attempts | Budgeted per network | 429 after repeated refusals, with Retry-After |
The monthly quota does not produce an error. A caller sees a normal 200 with a polite unavailability message, so if your integration starts returning that for everything, check the account rather than your code.
Exact numbers per plan are on Rate Limits, and every status and code is on Errors.
Errors
Failures use the OpenAI error envelope:
{
"error": {
"message": "Access denied for this request.",
"type": "authentication_error",
"param": null,
"code": "access_denied"
}
}All four fields are always present. param names the request field at fault on a 400, and is null when no single field is to blame.
A malformed key, an unknown key, an inactive agent and a disallowed origin all answer with the same 403 access_denied. That is deliberate, so a scraped key cannot be confirmed by the response. Check the credential type, key state, agent state, and allowed origin before retrying.
Keep the key in an environment variable and pass it as a bearer token per request. Never place a secret key in a query string, where it ends up in server logs and browser history.
There is no endpoint that writes configuration, and there is not going to be one in this surface. An API key can consume an agent, never reconfigure it, which is why a leaked key cannot be used to change your setup even though it can spend your allowance.