Documentation
Build with Bubble's API. Every new account gets $3.00 in free credit — no card required to start.
Quickstart
Get an API key, then send your first request.
- Sign up at /signup if you don't have an account.
- Go to Settings → API keys and create a key. Copy it immediately — you won't be able to see it again.
- Send a request:
curl https://grapelabs.us/api/v1/messages \
-H "Authorization: Bearer bb-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "bubble-2.0",
"max_tokens": 1024,
"messages": [
{ "role": "user", "content": "Hello, Bubble!" }
]
}'Or try it without writing any code in the Playground.
Authentication
Every request needs your API key in the Authorization header as a bearer token:
Authorization: Bearer bb-...Keys are created and revoked from Settings → API keys. You can create as many named keys as you want (e.g. one for production, one for testing) — revoking one doesn't affect the others.
Keep your key secret. Don't commit it to version control or expose it in client-side code — call the Bubble API from your own backend.
Create a message
POST /api/v1/messages — send a conversation, get a reply back.
| Parameter | Type | Required | Description |
|---|---|---|---|
| messages | array | Yes | Array of { role, content } objects. role is "user" or "assistant". |
| model | string | No | "bubble-2.0" or "pop-2.1" (cosmetic — both use the same model). Defaults to "bubble-2.0". |
| system | string | No | System prompt to steer behavior. |
| max_tokens | number | No | Max tokens to generate. 1–8192. Defaults to 4096. |
| stream | boolean | No | Set true for a server-sent-events stream instead of a single JSON response. |
Response shape:
{
"id": "msg_1234567890",
"model": "bubble-2.0",
"role": "assistant",
"content": [
{ "type": "text", "text": "Hi there! How can I help?" }
],
"stop_reason": "end_turn",
"usage": { "input_tokens": 12, "output_tokens": 9 }
}Streaming
Set "stream": true to get a server-sent-events stream instead of waiting for the full response:
curl https://grapelabs.us/api/v1/messages \
-H "Authorization: Bearer bb-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "bubble-2.0",
"stream": true,
"messages": [{ "role": "user", "content": "Write a haiku" }]
}'Events sent:
| Event | Data |
|---|---|
| delta | { "text": "..." } — a chunk of generated text |
| usage | { "input_tokens": N, "output_tokens": N } — sent once, near the end |
| done | {} — stream finished normally |
| error | { "message": "..." } — something went wrong |
Models
Bubble offers two model names. They're cosmetic — both currently route to the same underlying model, so pick whichever name fits your app.
| Model | Notes |
|---|---|
| bubble-2.0 | Default. Balanced & dependable. |
| pop-2.1 | Fast & punchy framing — same underlying model. |
Check usage
GET /api/v1/usage returns your remaining free credit without making a chat request:
curl https://grapelabs.us/api/v1/usage \
-H "Authorization: Bearer bb-YOUR_KEY"{
"total_credit_usd": 3.00,
"spent_usd": 0.0142,
"remaining_usd": 2.9858
}Errors
Errors come back as JSON with an HTTP status code and an error object:
{
"error": {
"message": "Invalid or revoked API key.",
"type": "authentication_error"
}
}| Status | Type | Meaning |
|---|---|---|
| 400 | invalid_request_error | Something's wrong with the request body. |
| 401 | authentication_error | Missing, malformed, or revoked API key. |
| 402 | insufficient_credit | You've used your free credit. |
| 502 | api_error | Something went wrong generating a response. Safe to retry. |
Pricing
Every new account starts with $3.00 in free credit. Usage is billed per token:
| Per million tokens | |
|---|---|
| Input | $3.00 |
| Output | $15.00 |
A typical short exchange (a few hundred tokens in, a few hundred out) costs a small fraction of a cent. Check exact spend any time via GET /api/v1/usage or Settings → API keys.
Code examples
No SDK required — it's a plain JSON REST API. Examples below.
JavaScript
const res = await fetch("https://grapelabs.us/api/v1/messages", {
method: "POST",
headers: {
"Authorization": "Bearer bb-YOUR_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "bubble-2.0",
messages: [{ role: "user", content: "Hello!" }],
}),
});
const data = await res.json();
console.log(data.content[0].text);Python
import requests
res = requests.post(
"https://grapelabs.us/api/v1/messages",
headers={"Authorization": "Bearer bb-YOUR_KEY"},
json={
"model": "bubble-2.0",
"messages": [{"role": "user", "content": "Hello!"}],
},
)
print(res.json()["content"][0]["text"])Streaming (JavaScript)
const res = await fetch("https://grapelabs.us/api/v1/messages", {
method: "POST",
headers: {
"Authorization": "Bearer bb-YOUR_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "bubble-2.0",
stream: true,
messages: [{ role: "user", content: "Write a haiku" }],
}),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const frames = buffer.split("\n\n");
buffer = frames.pop();
for (const frame of frames) {
const dataLine = frame.split("\n").find((l) => l.startsWith("data:"));
if (!dataLine) continue;
const data = JSON.parse(dataLine.slice(5));
if (data.text) process.stdout.write(data.text);
}
}