DEV Community

Roberto Francisco junior
Roberto Francisco junior

Posted on

Monitoring API credit usage before an automation runs out

A scraper can be healthy and still stop producing data because the account balance reached zero. HTTP availability does not tell you whether a job can be paid for. The MESSORA API exposes the account's current-month usage through GET /account/usage, so an automation can check its budget before submitting more work.

The endpoint returns the plan, monthly allowance, credits already used, remaining credits, and a per-endpoint breakdown. It is a small response, but it gives a better control signal than counting requests in the client.

Read the usage endpoint

import os
import requests

API = "https://api.messora.dev"
HEADERS = {"X-API-Key": os.environ["MESSORA_API_KEY"]}


def get_usage() -> dict:
    response = requests.get(
        f"{API}/account/usage",
        headers=HEADERS,
        timeout=30,
    )
    response.raise_for_status()
    return response.json()


usage = get_usage()
print(f"plan: {usage['plan']}")
print(f"used: {usage['credits_used']}")
print(f"remaining: {usage['remaining_credits']}")

for row in usage["breakdown"]:
    print(row["endpoint"], row["credits_used"])
Enter fullscreen mode Exit fullscreen mode

remaining_credits is the value to use for admission control. credits_used describes the current month, while monthly_credits is the plan allowance. The remaining value also includes top-up credits, so it is not always equal to monthly_credits - credits_used.

Reserve a local budget

Usage is a snapshot. Two workers can read the same remaining value and both decide to submit a job. The API remains authoritative, but a local reservation prevents your own workers from oversubscribing the same account.

class CreditBudget:
    def __init__(self, available: int, safety_margin: int = 0):
        self.available = available
        self.safety_margin = safety_margin

    def can_start(self, estimated_cost: int) -> bool:
        return estimated_cost <= self.available - self.safety_margin

    def reserve(self, estimated_cost: int) -> None:
        if not self.can_start(estimated_cost):
            raise RuntimeError(
                f"need {estimated_cost} credits, "
                f"only {max(self.available - self.safety_margin, 0)} are available"
            )
        self.available -= estimated_cost
Enter fullscreen mode Exit fullscreen mode

This is not a replacement for an atomic server-side balance check. It is a coordination layer inside one process or queue. If several machines submit jobs, put the reservation in shared storage or let each worker refresh usage immediately before enqueueing.

Estimate by endpoint

The usage breakdown also tells you which operation is consuming the account. A successful /scrape costs one credit. A batch or crawl charges successful pages, not merely the number of jobs submitted. A premium /search charges one credit per result returned and performs a balance pre-check using the requested num_results.

That difference matters for a scheduler. A queue containing one crawl with max_pages: 50 can consume up to 50 credits, while a queue containing ten empty or blocked pages may consume fewer. Use the endpoint's worst-case cost when admitting work, then reconcile against the actual job result.

Treat errors differently

A 401 from /account/usage means the X-API-Key header is absent. A 403 means the key is invalid or revoked. Neither should be retried with a shorter delay; fix the credential or configuration. A 429 is the usage endpoint's own rate limit, documented as 30 requests per minute per tenant. Cache the last successful snapshot for a short interval instead of calling it before every page.

A practical policy is to refresh usage when a worker starts, before a large batch, and after a job completes. Log plan, credits_used, remaining_credits, and the endpoint breakdown, but never log the API key. When the local reservation cannot cover the worst-case cost, leave the job queued and surface a clear budget warning rather than allowing a predictable mid-run failure.

Budget checks turn "the scraper stopped" into an observable state: no balance, invalid credential, rate limit, or real extraction failure. Each case has a different operator action, and /account/usage supplies the evidence needed to choose it.

Top comments (0)