DEV Community

dodou
dodou

Posted on

Know Your Balance Before You Spend: GET /account/credits in Practice

Every SERP API bill ends the same way: you find out you're out of credits when a request fails with 1020. But there's a way to see the number before that happens, and it costs nothing to check — GET /account/credits is a documented utility endpoint that doesn't consume search credits at all.

I wired this into my monitoring scripts last month after a nightly job died at 2am on an empty balance. Now the job checks the balance first, alerts before the threshold, and I haven't been surprised since.

What the endpoint gives you

Per the docs, GET /account/credits returns the current balance for the account that owns an active API key. The response separates what you have into:

  • credits — the total you can spend right now
  • permanent_credits — the portion with no calendar expiry
  • expiring_credits — the portion tied to a subscription cycle, with expiring_expires_at as a Unix timestamp in seconds; it's 0 when no limited-time credits are active

That split matters. If your balance looks healthy but most of it is expiring credits, you have a scheduling problem, not a budget problem — use the permanent balance for long-term plans and burn the expiring one first.

Auth is the same as everywhere else: the key goes in the X-API-Key header. URL query parameters are not accepted, so don't try ?api_key=. The endpoint is rate limited to 60 requests per minute per account — plenty for a pre-flight check, and the docs confirm it doesn't consume search credits. Field definitions are in SerpBase's endpoint documentation.

The pre-flight check

import requests

API = "https://api.serpbase.dev"
KEY = "your_api_key"   # your key here

def balance() -> dict:
    resp = requests.get(f"{API}/account/credits",
                        headers={"X-API-Key": KEY}, timeout=15)
    data = resp.json()
    if data.get("status") != 0:
        raise RuntimeError(f"{data.get('status')}: {data.get('error')}")
    return data

if __name__ == "__main__":
    b = balance()
    print(f"total: {b['credits']}")
    print(f"permanent: {b['permanent_credits']}")
    print(f"expiring: {b['expiring_credits']} (expires_at={b['expiring_expires_at']})")
Enter fullscreen mode Exit fullscreen mode

Three ways I use it in practice:

  1. Pre-flight guard. Before a batch job starts, fetch the balance and compare it against len(keywords) * credits_per_request. If the balance is short, alert and skip the run instead of dying halfway through with a partial CSV.
  2. Expiry awareness. When expiring_credits is non-zero, log expiring_expires_at next to the daily run — if you're burning 40 credits a day with 400 expiring, you have ten days to use them, and that's a signal to move monitoring to a weekly cadence rather than buy more.
  3. Post-run reconciliation. After a batch, subtract the run's successful request count from the starting balance. If the numbers don't line up, the request_id on each response is the stable identifier to correlate with support.

FAQ

Does checking the balance really cost nothing? Yes — the docs list it as 0 credits. That's the whole point: you can check as often as you need inside the 60 requests/minute limit.

Why did my balance check fail with 1001? Same as any other endpoint — missing, invalid, or revoked key. The header name is X-API-Key; query-string keys are not accepted.

Can I check the balance from the MCP server? The MCP tools cover the six search endpoints. For balance checks, call this endpoint directly — it's a plain GET with the same key.

How often should a scheduled job check? Once per run is enough for a daily job. If you run hourly batches, once an hour stays far below the 60/min limit.

Add the pre-flight guard to your next batch script tonight — it's four lines, and it turns "why did my job die" into "my job told me it was low before it started".

Top comments (0)