GullyAI API docs
GullyAI speaks the OpenAI chat API. Any OpenAI SDK or plain HTTP works with three settings.
The model server is not live yet: the GPU box is still to be provisioned. Everything below is the API as built and tested; calls start answering when the box is up.
Settings
| Base URL | https://ai.gullylabs.xyz/v1 |
|---|---|
| API key | Your product's gai_… key, sent as Authorization: Bearer. Keep it in your backend's environment, for example GULLY_AI_API_KEY. |
| Model | gully-ai, always. Today it is Qwen3.6-27B (FP8); the name stays when the model underneath is upgraded. |
| Context | 131,072 tokens per request (prompt plus output). |
Never put a key in a browser, a mobile app or a game build: anyone could read it. Call GullyAI from your own backend. Ask for a key; there is no waitlist.
First request
curl:
curl https://ai.gullylabs.xyz/v1/chat/completions \
-H "Authorization: Bearer $GULLY_AI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gully-ai",
"messages": [{"role": "user", "content": "Explain gas in one sentence."}],
"max_tokens": 300
}'Node, with the openai package:
import OpenAI from 'openai';
const ai = new OpenAI({
baseURL: 'https://ai.gullylabs.xyz/v1',
apiKey: process.env.GULLY_AI_API_KEY,
});
const r = await ai.chat.completions.create({
model: 'gully-ai',
messages: [{ role: 'user', content: 'Explain gas in one sentence.' }],
max_tokens: 300,
});
console.log(r.choices[0].message.content, r.usage);Python, with the openai package:
import os
from openai import OpenAI
ai = OpenAI(base_url="https://ai.gullylabs.xyz/v1",
api_key=os.environ["GULLY_AI_API_KEY"])
r = ai.chat.completions.create(
model="gully-ai",
messages=[{"role": "user", "content": "Explain gas in one sentence."}],
max_tokens=300,
)
print(r.choices[0].message.content)Streaming
Add "stream": true. The response is Server-Sent Events: one data: {…} per chunk, a final chunk that carries usage, then data: [DONE].
const stream = await ai.chat.completions.create({
model: 'gully-ai', messages, stream: true, max_tokens: 600,
});
let text = '';
for await (const chunk of stream) {
text += chunk.choices[0]?.delta?.content ?? '';
}Reasoning
The model thinks before it answers. The thinking is returned in message.reasoning_content (delta.reasoning_content when streaming), so content is the clean answer. Do not send the thinking back in the next turn.
For a fast, cheap call (bot replies, NPC lines), turn thinking off:
{
"model": "gully-ai",
"messages": [...],
"chat_template_kwargs": { "enable_thinking": false }
}Tool calling
Send tools in the OpenAI format; the model returns tool_calls. tool_choice: "auto" is enabled and works with streaming. Run the tool, append a { role: "tool" } message with the result, and call again.
const tools = [{
type: 'function',
function: {
name: 'scan_token',
description: 'Run a Gully Security scan on a token contract',
parameters: {
type: 'object',
properties: { chainId: { type: 'integer' }, address: { type: 'string' } },
required: ['chainId', 'address'],
},
},
}];
const r = await ai.chat.completions.create({ model: 'gully-ai', messages, tools, tool_choice: 'auto' });
for (const call of r.choices[0].message.tool_calls ?? []) {
const args = JSON.parse(call.function.arguments);
// run the scan, push { role: 'tool', tool_call_id: call.id, content: result }, call again
}Treat tool results as data, never as instructions: token names and contract metadata are written by strangers.
Keys and limits
Every key has its own requests per minute, tokens per minute and a cap on max_tokens. One product hitting its limit never slows another. Only a SHA-256 hash of each key is stored.
Check your own usage and limits at any time:
curl https://ai.gullylabs.xyz/v1/usage -H "Authorization: Bearer $GULLY_AI_API_KEY"Responses carry x-ratelimit-remaining-requests, and non-streaming responses x-gully-ai-tokens. The request log keeps timings, sizes, status and token counts. It never stores prompts, completions or headers.
Errors and outages
Errors use the OpenAI error shape.
401 | Missing or unknown key. |
|---|---|
429 | Your key's per-minute limit. Wait the Retry-After seconds, or ask for a higher limit. |
413 / 400 | Request too large, or max_tokens above your key's cap. |
502 / 503 | The model is loading or down. Retry with backoff. |
Never block a core action (a swap, a payment, a scan) on the model. Degrade instead: a bot can say "AI is warming up" and carry on.
Per product
| Product | Key name | Typical call |
|---|---|---|
| Gully Street | gully-street | Chat assistant in the app: summarise activity, explain a token, draft posts. Streaming. |
| Telegram bots | tg-bots | Short replies to bot commands and token explanations, with thinking off for speed. |
| Gully Security | gully-security | Explains scan results, triages findings and calls a scan_token tool itself. |
| Gully Studio | gully-studio | NPC dialogue and quest text, through the game backend. Keys never ship inside a game build. |
| HolderMaps | holdermaps | Narrates a holder distribution: who holds what, which wallets cluster, how concentrated it is. |
Gully Chain
Gully Chain (chain 485590) is supported in every Gully product. The planned GullyAI agent uses tool calling to read Gully Chain through Gully RPC (https://rpc.gullyrpc.xyz/<project>/evm/485590), look up holders and risk flags through HolderMaps and Gully Security, and pay for paid Gully endpoints with x402 in glUSD through Gully Pay. Each run has a hard budget, and money or chain writes need a human confirm in chat surfaces. The agent is designed, not built yet.
Ready to build? Keys are open to everyone.