API PlaygroundConfigure an endpoint and get ready-to-run code
true
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.

Profile menu with default API key

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:

statusMeaning
pendingTask is queued or running. Includes a stage and optional info field.
successTask completed. Result is in the data field.
errorTask failed. Reason is in the message field.
timeoutTask 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
ParameterTypeRequiredDescription
urlstringone of url / url_idThe page URL to score
url_idnumberone of url / url_idA previously submitted URL by its Bittlebits ID
link_userbooleannoAssociate 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

FieldTypeDescription
url_idnumberThe Bittlebits URL ID
suggestionsarrayList of suggested text changes
original_htmlstringRaw HTML of the page as fetched
original_markdownstringPage content as parsed markdown
rewritten_htmlstringFull page HTML with all suggestions applied
rewritten_markdownstringPage markdown with all suggestions applied

Each suggestion object:

FieldTypeDescription
stable_idstringStable identifier for the element
current_textstringThe existing text to replace
suggested_textstringThe replacement text
rationalestringWhy this change improves GEO performance
is_additionbooleantrue if this is a new element rather than an edit
location_hintstringFor 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" }
StatusMeaning
400 Bad RequestMissing or invalid parameters (e.g. neither url nor url_id provided)
401 UnauthorizedMissing or invalid API key
429 Too Many RequestsRate limit exceeded — back off and retry
500 Internal Server ErrorSomething went wrong on our end
Stream error eventTask failed during processing — message contains the reason
Stream timeout eventTask exceeded the maximum wait time — resubmit to retry