/ Docs

API reference

HTTP + JSON. Bearer auth with an API key from /dashboard/settings. Every endpoint here is callable with the curl example as-is — just swap rkidx_… for your real key.

Base URL: https://rapidseoindexer.com/api/v1

Authentication

All requests authenticate with a bearer token in the Authorization header. We accept two token types:

  • API keys (prefix rkidx_) — long-lived, scoped to your account. Best for servers, CI, and integrations. Generate in /dashboard/settings.
  • Supabase JWT— issued by our auth flow. Used by the web app; you usually don't want this for server-to-server.

API keys never expire on their own. Rotate by deleting + regenerating.

bash
curl https://rapidseoindexer.com/api/v1/credits/balance \
  -H "Authorization: Bearer rkidx_YOUR_API_KEY"

Errors

Errors return a JSON body with a detail string. The HTTP status is the source of truth — body fields are for humans, not branching logic.

http
HTTP/1.1 401 Unauthorized
X-Request-ID: 8f9a3c4d2b1e
Content-Type: application/json

{"detail": "Invalid API key"}

Common statuses:

  • 400 — invalid input
  • 401 — missing or invalid token
  • 403 — disabled user
  • 404 — resource not found / not yours
  • 409 — concurrent request with the same Idempotency-Key
  • 422 — body validation failed (or same Idempotency-Key with different body)
  • 429 — rate-limited
  • 500 — our fault; the response body + every 5xx response header includes X-Request-ID — quote it in support tickets and we can pull the full trace.

Idempotency

POST endpoints that change state accept an Idempotency-Key header (opaque, 8–128 chars of [A-Za-z0-9_-]). The first call processes normally; subsequent calls with the same key + same body within 24h return the cached response verbatim and add Idempotent-Replayed: true.

Reusing the same key with a different body returns 422— that's almost always a client bug worth surfacing loud. Concurrent retries get 409 until the first finishes.

Recommended pattern: one UUID per request, generated by the client.

bash
curl -X POST https://rapidseoindexer.com/api/v1/urls \
  -H "Authorization: Bearer rkidx_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"urls":["https://example.com/page"],"mode":"standard"}'

Submit URLs

POST/v1/urls

Submit one or more URLs to the indexing pipeline. Returns a per-URL breakdown of which were accepted, which were rejected (and why), and how many credits were charged.

Body:

  • urls — array of absolute URLs (1–10000). Each URL is normalized + validated before charging.
  • mode "standard" (1 credit) or "apex" (3 credits; adds satellite + Google Indexing API fan-out).
  • project_id — optional UUID to group submissions.
Request
curl -X POST https://rapidseoindexer.com/api/v1/urls \
  -H "Authorization: Bearer rkidx_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7f23-batch-2026-05-19" \
  -d '{
    "urls": [
      "https://www.etsy.com/listing/12345/my-product",
      "https://www.etsy.com/listing/67890/another-product"
    ],
    "mode": "apex"
  }'
Response · 201
{
  "accepted": 2,
  "rejected": 0,
  "credits_charged": 6,
  "items": [
    {
      "url": "https://www.etsy.com/listing/12345/my-product",
      "accepted": true,
      "submission_id": "a4f1...-...-...-b9c0",
      "reason": null,
      "detail": null
    },
    {
      "url": "https://www.etsy.com/listing/67890/another-product",
      "accepted": true,
      "submission_id": "b5e2...-...-...-c0d1",
      "reason": null,
      "detail": null
    }
  ]
}

Rejection reasons include duplicate_indexed (same URL was indexed in the last 30 days), duplicate_in_progress, blacklisted, insufficient_credits. No credit is charged for rejected URLs.

List URLs

GET/v1/urls

Lists your URL submissions, newest first. Defaults to your customer URLs only — internal fan-out (gist, telegraph, pastebin) is hidden unless you opt in with include_satellites=true.

Query parameters:

  • limit (default 100, max soft-bounded by server)
  • offset
  • status_filter — one of pending, submitting, submitted, indexed, failed, refunded
  • project_id — UUID
bash
curl "https://rapidseoindexer.com/api/v1/urls?status_filter=indexed&limit=50" \
  -H "Authorization: Bearer rkidx_YOUR_API_KEY"

Get URL detail

GET/v1/urls/{id}

Per-URL detail including computed pipeline stages (Discovery → Backlinks → Search engines → Verification). The stages are aggregated from internal logs — we deliberately don't expose the exact backlink destinations.

bash
curl https://rapidseoindexer.com/api/v1/urls/a4f1abcd-1234-5678-90ab-cdef01234567 \
  -H "Authorization: Bearer rkidx_YOUR_API_KEY"
Response · 200
{
  "id": "a4f1abcd-...",
  "url": "https://www.etsy.com/listing/12345/my-product",
  "domain": "www.etsy.com",
  "status": "indexed",
  "mode": "apex",
  "credits_used": 3,
  "credits_refunded": 0,
  "refunded": false,
  "submitted_at": "2026-05-18T14:23:01Z",
  "indexed_at": "2026-05-18T15:47:33Z",
  "last_checked_at": "2026-05-18T15:47:33Z",
  "next_check_at": null,
  "created_at": "2026-05-18T14:22:55Z",
  "stages": [
    {"key":"discovery","label":"Indexing pings sent","status":"done","attempts":5,"successes":4},
    {"key":"backlinks","label":"Backlinks built","status":"done","attempts":12,"successes":12},
    {"key":"search_engines","label":"Search engines notified","status":"done","attempts":4,"successes":3},
    {"key":"verification","label":"Indexed by Google","status":"done","attempts":1,"successes":1}
  ]
}

Credit balance

GET/v1/credits/balance

bash
curl https://rapidseoindexer.com/api/v1/credits/balance \
  -H "Authorization: Bearer rkidx_YOUR_API_KEY"
Response · 200
{
  "credits": 1180,
  "lifetime_purchased": 2500,
  "lifetime_spent": 1320,
  "lifetime_refunded": 0
}

Credit transactions

GET/v1/credits/transactions

Returns the most recent debits, refunds, purchases, and admin adjustments. Each row includes the related URL ID when applicable — useful for reconciling your invoicing against actual submissions.

bash
curl https://rapidseoindexer.com/api/v1/credits/transactions \
  -H "Authorization: Bearer rkidx_YOUR_API_KEY"

List credit packs

GET/v1/credits/packs

Public — no auth required. Returns the credit packs currently for sale plus whether each payment provider is live.

bash
curl https://rapidseoindexer.com/api/v1/credits/packs

Projects

Optional grouping: tag URLs with a project_id so you can filter them in the dashboard + reports.

POST/v1/projects

bash
curl -X POST https://rapidseoindexer.com/api/v1/projects \
  -H "Authorization: Bearer rkidx_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Etsy listings — Q2 2026"}'

GET/v1/projects

DELETE/v1/projects/{id}

Webhooks

Subscribe to terminal-state events on your URLs. url.indexed, url.failed, url.refunded. Bodies are HMAC-SHA256 signed with the webhook's secret.

POST/v1/webhooks

bash
curl -X POST https://rapidseoindexer.com/api/v1/webhooks \
  -H "Authorization: Bearer rkidx_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Slack inbox",
    "url": "https://hooks.slack.com/services/T0/B0/secret",
    "events": ["url.indexed", "url.failed"]
  }'

The response includes a one-time secret — store it now, it's never returned again. Use /rotate-secret to mint a new one if you lose it.

Signature verification

Every delivery includes X-Webhook-Signature: sha256=<hex>, X-Webhook-Event, and X-Webhook-Id. Verify the signature against the raw body using your stored secret:

Python (FastAPI/Flask)
import hmac, hashlib
from fastapi import Request, HTTPException

SECRET = "wh_secret_from_creation"

async def verify(request: Request):
    body = await request.body()
    expected = "sha256=" + hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
    got = request.headers.get("X-Webhook-Signature", "")
    if not hmac.compare_digest(expected, got):
        raise HTTPException(401, "bad signature")
    return body
Node.js (Express)
import crypto from "node:crypto";

const SECRET = "wh_secret_from_creation";

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const expected = "sha256=" + crypto
    .createHmac("sha256", SECRET)
    .update(req.body)
    .digest("hex");
  const got = req.headers["x-webhook-signature"] ?? "";
  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(got))) {
    return res.status(401).send("bad signature");
  }
  const payload = JSON.parse(req.body.toString());
  // handle event ...
  res.json({ ok: true });
});

Event body shape

json
{
  "event": "url.indexed",
  "delivered_at": "2026-05-19T22:00:00Z",
  "data": {
    "submission_id": "a4f1...-...-...-b9c0",
    "url": "https://www.etsy.com/listing/12345/my-product",
    "indexed_at": "2026-05-19T21:58:42Z",
    "minutes_to_index": 87.3
  }
}

Auto-disable

After 10 consecutive failures (non-2xx response, timeout, or network error), a webhook is automatically disabled to stop it burning worker time. Re-enable via PATCH (sets is_active: true, resets the counter) or rotate the URL if your receiver moved.

Management

GET/v1/webhooks

PATCH/v1/webhooks/{id}

POST/v1/webhooks/{id}/test

POST/v1/webhooks/{id}/rotate-secret

DELETE/v1/webhooks/{id}

The /test endpoint fires a synthetic webhook.test event so you can verify your receiver before any real URL flips through.