const url = 'https://example.com/page';
const res = await fetch(`https://bittlebits.ai/api/v1/score?url=${encodeURIComponent(url)}`, {
method: 'POST',
headers: { 'X-API-Key': YOUR_API_KEY },
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
for (const line of decoder.decode(value).split('\n')) {
if (!line.startsWith('data: ')) continue;
const event = JSON.parse(line.slice(6));
if (event.status === 'success') {
console.log(event.data);
break;
}
if (event.status === 'error') throw new Error(event.message);
}
}API Reference
The Bittlebits REST API lets you programmatically score pages and retrieve rewrite suggestions. All endpoints are available at https://bittlebits.ai/api/v1/.
Authentication
All registered users are given a Bittlebits API key by default. To locate yours, click the profile icon in the header, and click on My Account.
Here you can peek at your API key, copy it, or rotate it to a different key.
All requests require an API key. Pass it as one of the following headers:
Authorization: YOUR_API_KEY
X-API-Key: YOUR_API_KEY
How it works
Both endpoints use Server-Sent Events (SSE). Make a single POST request and keep the connection open — the server streams progress updates and delivers the result when ready, no polling required.
The response is a stream of newline-delimited JSON events, each prefixed with data: . Events have a status field:
status | Meaning |
|---|---|
pending | Task is queued or running. Includes a stage and optional info field. |
success | Task completed. Result is in the data field. |
error | Task failed. Reason is in the message field. |
timeout | Task exceeded the maximum wait time. Resubmit to retry. |
data: {"status": "pending", "stage": "url", "task_id": "...", "info": {...}}
data: {"status": "pending", "stage": "rewrite", "task_id": "...", "info": {...}}
data: {"status": "success", "data": { ... }}
Score
Score a page against Bittlebits' GEO metrics. Each metric is rated 0–10.
POST /v1/score
Rate limit: 100 requests/hour per IP.
POST /v1/score?url=https://example.com/page
X-API-Key: YOUR_API_KEY
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | one of url / url_id | The page URL to score |
url_id | number | one of url / url_id | A previously submitted URL by its Bittlebits ID |
link_user | boolean | no | Associate this URL with your account (default: true) |
Example
const res = await fetch('https://bittlebits.ai/api/v1/score?url=https://example.com/page', {
method: 'POST',
headers: { 'X-API-Key': YOUR_API_KEY },
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
for (const line of decoder.decode(value).split('\n')) {
if (!line.startsWith('data: ')) continue;
const event = JSON.parse(line.slice(6));
if (event.status === 'success') {
console.log(event.data); // { url_id, metrics }
break;
}
if (event.status === 'error') throw new Error(event.message);
}
}
import json
import requests
with requests.post(
'https://bittlebits.ai/api/v1/score',
params={'url': 'https://example.com/page'},
headers={'X-API-Key': YOUR_API_KEY},
stream=True,
) as res:
for line in res.iter_lines():
if not line.startswith(b'data: '):
continue
event = json.loads(line[6:])
if event['status'] == 'success':
print(event['data']) # { 'url_id': 42, 'metrics': { ... } }
break
if event['status'] == 'error':
raise RuntimeError(event['message'])
Event — pending
{"status": "pending", "stage": "rewrite", "task_id": "abc123", "info": {"message": "Analyzing page..."}}
Event — success
{
"status": "success",
"data": {
"url_id": 42,
"display_name": "example.com/page",
"metrics": {
"citation_readiness": 8.2,
"content_depth": 6.5,
"entity_clarity": 7.1
},
"checklist": []
}
}
Rewrite
Retrieve GEO rewrite suggestions for a page.
POST /v1/rewrite
Rate limit: 100 requests/hour per IP.
POST /v1/rewrite?url=https://example.com/page
X-API-Key: YOUR_API_KEY
Same parameters as POST /v1/score.
const res = await fetch('https://bittlebits.ai/api/v1/rewrite?url=https://example.com/page', {
method: 'POST',
headers: { 'X-API-Key': YOUR_API_KEY },
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
for (const line of decoder.decode(value).split('\n')) {
if (!line.startsWith('data: ')) continue;
const event = JSON.parse(line.slice(6));
if (event.status === 'success') {
console.log(event.data); // { url_id, suggestions, original_html, ... }
break;
}
if (event.status === 'error') throw new Error(event.message);
}
}
Event — success
{
"status": "success",
"data": {
"url_id": 42,
"suggestions": [
{
"stable_id": "hero-h1",
"current_text": "Welcome to our site",
"suggested_text": "The leading platform for X",
"rationale": "More specific entity signal for AI citation",
"is_addition": false
}
],
"original_html": "<!DOCTYPE html><html>...</html>",
"original_markdown": "# Original page content...",
"rewritten_html": "<!DOCTYPE html><html>...</html>",
"rewritten_markdown": "# The leading platform for X..."
}
}
data fields
| Field | Type | Description |
|---|---|---|
url_id | number | The Bittlebits URL ID |
suggestions | array | List of suggested text changes |
original_html | string | Raw HTML of the page as fetched |
original_markdown | string | Page content as parsed markdown |
rewritten_html | string | Full page HTML with all suggestions applied |
rewritten_markdown | string | Page markdown with all suggestions applied |
Each suggestion object:
| Field | Type | Description |
|---|---|---|
stable_id | string | Stable identifier for the element |
current_text | string | The existing text to replace |
suggested_text | string | The replacement text |
rationale | string | Why this change improves GEO performance |
is_addition | boolean | true if this is a new element rather than an edit |
location_hint | string | For additions: where on the page the element should appear |
Errors
Non-stream errors (auth, bad params) return a JSON body with an error field. Stream errors arrive as a {"status": "error", "message": "..."} event.
{ "error": "Human-readable description of the error" }
| Status | Meaning |
|---|---|
400 Bad Request | Missing or invalid parameters (e.g. neither url nor url_id provided) |
401 Unauthorized | Missing or invalid API key |
429 Too Many Requests | Rate limit exceeded — back off and retry |
500 Internal Server Error | Something went wrong on our end |
Stream error event | Task failed during processing — message contains the reason |
Stream timeout event | Task exceeded the maximum wait time — resubmit to retry |