---
title: "Scheduled AI Agent Runs: Budgeting Credits per Run"
description: "Cap an agent at N calls per run with a Python ledger that reads X-Credits-Used, plus the math for hourly and daily runs on 1,000 free credits."
url: https://perceptlayer.com/blog/scheduled-agent-credit-budget
---

[Blog](https://perceptlayer.com/blog) / [AI agents](https://perceptlayer.com/blog/category/ai-agents)

# Scheduled AI Agent Runs: Budgeting Credits per Run

30 Jul 2026 Perceptlayer 7 min read

Budget a scheduled agent by dividing your monthly credits by the number of runs, then enforcing the result as a hard call cap inside each run. On 1,000 free credits, an hourly agent gets 1 call per run and a daily agent gets 32. A small Python ledger that reads the `X-Credits-Used` header keeps every run inside that cap.

## Key takeaways

-   Per-run cap = monthly credits ÷ runs per month, rounded down. A 31-day month has 744 hourly runs or 31 daily runs.
-   The free tier's 1,000 credits cover 744 hourly runs at 1 call each, or 31 daily runs at up to 32 calls each.
-   Count every attempt against the cap, and treat a timed-out call as spent, because a request whose reply was lost may have been billed.
-   Read `X-Credits-Used` and `X-Credits-Remaining` on each response so the ledger follows the real balance, not your estimate.
-   The service is prepaid. At a zero balance calls return `insufficient_credits` (402), so the balance is the absolute ceiling on spend.

## How do you set a credit budget for each agent run?

Start from the monthly credits you are willing to spend and divide by the number of scheduled runs in the month. Round down, and that is the most calls one run may make. A successful call costs the credits shown on its endpoint, which is 1 credit for every [Google Search API](https://perceptlayer.com/apis/google-search) endpoint, so the call cap and the credit cap are the same number.

```text
runs per month  = runs per day × days in the month
calls per run   = floor(monthly credits ÷ runs per month)
monthly spend   = runs per month × calls per run
```

Failed calls cost nothing, so a cap based on attempts is slightly conservative. That is the right direction to be wrong in. If you want the broader picture of spend limits for agents, [AI agent API cost control](https://perceptlayer.com/blog/ai-agent-web-data-costs) covers it; this article is the per-run, scheduled case.

## What does an hourly or daily schedule cost against the free credits?

Every account gets 1,000 free credits a month after verifying an email address, with no credit card. Spread over a 31-day month, that is enough for one call on each of 744 hourly runs, or 32 calls on each of 31 daily runs. The table shows the arithmetic for other schedules and for prepaid packs.

| Schedule | Runs in a 31-day month | Credits available | Calls per run | Credits used |
| --- | --- | --- | --- | --- |
| Daily | 31 | 1,000 (free) | 32 | 31 × 32 = 992 |
| Every 6 hours | 124 | 1,000 (free) | 8 | 124 × 8 = 992 |
| Hourly | 744 | 1,000 (free) | 1 | 744 × 1 = 744 |
| Every 15 minutes | 2,976 | 1,000 (free) | 0 | Not possible on the free credits |
| Daily | 31 | 10,000 (Starter pack) | 322 | 31 × 322 = 9,982 |
| Hourly | 744 | 10,000 (Starter pack) | 13 | 744 × 13 = 9,672 |
| Hourly | 744 | 50,000 (Build pack) | 67 | 744 × 67 = 49,848 |

Two things stand out. Frequency matters far more than depth: hourly runs are 24 times as frequent as daily ones, so the per-run allowance falls from 32 calls to 1 on the free credits, and from 322 to 13 on the Starter pack. And a 15-minute schedule needs at least 2,976 credits a month before it can make a single call per run. [Pricing](https://perceptlayer.com/pricing) has what credits cost; purchased credits last 12 months, so an unspent month doesn't vanish.

## Which request will the agent spend its credits on?

For a research agent, the workhorse is `GET /v1/google-search/search`, which returns organic results, related searches, local results and more as JSON for 1 credit. The parameters below are the ones an agent usually sets. The full list is on the [Search endpoint page](https://perceptlayer.com/apis/google-search/search).

| Parameter | Type | Required | Notes |
| --- | --- | --- | --- |
| `query` | string | yes | The search text. |
| `hl` | string | no | Interface language, up to 2 characters. |
| `gl` | string | no | Country, up to 2 characters. |
| `safe` | string | no | One of `active`, `off`. |
| `page` | integer | no | Result page. |
| `amount` | integer | no | Integer parameter, see the endpoint page. |

A single call looks like this:

```bash
curl "https://api.perceptlayer.com/v1/google-search/search?query=best+restaurants+in+Berlin&hl=en&safe=off" \
  -H "X-API-Key: $API_KEY"
```

The response is a JSON object with named fields. A recorded response, trimmed:

```json
{
  "search_information": {
    "query_displayed": "best restaurants in Berlin",
    "organic_results_count": 6
  },
  "organic_results": [
    {
      "position": 1,
      "title": "The Best Restaurants in Berlin - Food Guide & Map",
      "link": "https://andershusa.com/best-restaurants-berlin-germany-guide/",
      "snippet": "Dec 3, 2025 — Tim Raue is the most internationally renowned restaurant in Berlin. …",
      "source": "Anders & Kaitlin"
    },
    …
  ],
  "related_searches": […],
  …
}
```

The cost is on the response headers, not in the body: `X-Credits-Used` is what this call cost and `X-Credits-Remaining` is the balance afterwards. Those two headers are all the ledger below needs. The [credits documentation](https://docs.perceptlayer.com/credits) describes them.

## How do you enforce a per-run call cap in Python?

Wrap every request in one function that checks the cap before the call and records the cost after it. Three limits do the work: a maximum number of attempts per run, a monthly credit limit read from a local ledger, and a reserve you never touch. When any of them trips, the function raises an exception instead of making the request.

```python
import os
import sqlite3
import uuid

import requests

BASE = "https://api.perceptlayer.com"
KEY = os.environ["API_KEY"]

class BudgetExceeded(Exception):
    pass

class Ledger:
    def __init__(self, path="credits.db"):
        self.db = sqlite3.connect(path)
        self.db.execute(
            """CREATE TABLE IF NOT EXISTS calls (
                ts TEXT, run_id TEXT, path TEXT,
                status INTEGER, credits INTEGER, remaining INTEGER)"""
        )

    def add(self, run_id, path, status, credits, remaining):
        self.db.execute(
            "INSERT INTO calls VALUES (datetime('now'), ?, ?, ?, ?, ?)",
            (run_id, path, status, credits, remaining),
        )
        self.db.commit()

    def month_spent(self):
        row = self.db.execute(
            "SELECT COALESCE(SUM(credits), 0) FROM calls "
            "WHERE ts >= date('now', 'start of month')"
        ).fetchone()
        return row[0]

class RunBudget:
    def __init__(self, ledger, max_calls, month_limit, reserve=0):
        self.ledger = ledger
        self.max_calls = max_calls
        self.month_limit = month_limit
        self.reserve = reserve
        self.run_id = uuid.uuid4().hex[:8]
        self.calls = 0
        self.remaining = None  # last balance seen in a response header

    def get(self, path, **params):
        if self.calls >= self.max_calls:
            raise BudgetExceeded(f"call cap of {self.max_calls} reached")
        if self.ledger.month_spent() + 1 > self.month_limit:
            raise BudgetExceeded("monthly credit limit reached")
        if self.remaining is not None and self.remaining <= self.reserve:
            raise BudgetExceeded("balance is at the reserve")

        self.calls += 1
        try:
            r = requests.get(
                BASE + path,
                params=params,
                headers={"X-API-Key": KEY},
                timeout=30,
            )
        except requests.Timeout:
            # The reply was lost; the call may have succeeded and been billed.
            self.ledger.add(self.run_id, path, 0, 1, None)
            raise

        used = int(r.headers.get("X-Credits-Used", 0))
        left = r.headers.get("X-Credits-Remaining")
        if left is not None:
            self.remaining = int(left)
        self.ledger.add(self.run_id, path, r.status_code, used, self.remaining)

        if r.status_code == 402:
            raise BudgetExceeded("insufficient_credits")
        r.raise_for_status()
        return r.json()
```

The timeout branch is the part most budget code gets wrong. Requests have no idempotency key, so a client timeout can hide a call that went through. The ledger records it at the endpoint's 1 credit, the worst case, and the next successful response's `X-Credits-Remaining` corrects the picture. The `timeout=30` argument bounds how long `requests` waits for the server ( [Requests timeouts](https://requests.readthedocs.io/en/latest/user/advanced/#timeouts)). [Retry web API calls without paying twice](https://perceptlayer.com/blog/retry-web-api-calls-without-paying-twice) goes deeper on that.

The ledger's "start of month" is the calendar month. Treat the ledger as a local guard rail and `X-Credits-Remaining` as the source of truth. SQLite's date functions are documented in the [SQLite date and time functions reference](https://www.sqlite.org/lang_datefunc.html) if you want to move the window.

## How do you hand the capped function to an agent?

Expose one tool to the model, backed by `RunBudget.get`, and make the cap visible in the tool's result. When the budget is spent, return a plain message instead of raising, so the model finishes with what it has instead of looping.

```python
def make_search_tool(budget):
    def search_web(query: str) -> str:
        try:
            data = budget.get(
                "/v1/google-search/search", query=query, hl="en"
            )
        except BudgetExceeded as e:
            return f"No more searches this run ({e}). Answer from what you have."
        except requests.RequestException as e:
            return f"Search failed ({type(e).__name__}). Answer from what you have."
        rows = data.get("organic_results", [])[:5]
        return "\n".join(
            f"{r['position']}. {r['title']} - {r['link']}\n   {r.get('snippet', '')}"
            for r in rows
        )

    return search_web

ledger = Ledger()
budget = RunBudget(ledger, max_calls=32, month_limit=992, reserve=8)
search_web = make_search_tool(budget)
```

Passing only five rows with four fields also keeps the model's context small, which is a separate saving covered in [trimming API responses for LLM context](https://perceptlayer.com/blog/trim-api-responses-llm-context). For the tool definition itself, see the [tool schema checklist](https://perceptlayer.com/blog/agent-tool-schema-design-web-apis).

If you connect the agent through the [MCP server](https://docs.perceptlayer.com/ai-agents) instead, the same rule applies. `search_endpoints` and `get_endpoint` only look up the catalogue and cost nothing; `call_endpoint` spends credits on success and reports the credits used and remaining, so your harness can tally those numbers into the same ledger and stop the loop at the cap. The [MCP vs function calling comparison](https://perceptlayer.com/blog/mcp-vs-function-calling-web-data) helps you choose between the two.

## What should happen when a run hits a limit?

End the run, log why, and let the next scheduled run start fresh. Don't retry inside the same run to "finish the job", because that is how a capped run turns into an uncapped one. Each limit maps to a different response, and the [error reference](https://docs.perceptlayer.com/errors) lists the codes.

| Signal | Meaning | What the run does |
| --- | --- | --- |
| `BudgetExceeded` from the call cap | The run used its allowance | Return a partial answer and exit |
| 402 `insufficient_credits` | Balance is empty; nothing more is debited | Stop, alert a human, no retry |
| 429 `rate_limited` | Too many requests this minute | End the run and let the next one start fresh |
| 503 `temporarily_unavailable` | The service is briefly unavailable | End the run and let the next one start fresh |
| Timeout | Reply lost, may have been billed | Log at 1 credit, do not repeat blindly |

The 429 and 503 responses carry a `Retry-After` header with the seconds to wait. Free accounts get 1 request a second (60 a minute); [rate limits](https://docs.perceptlayer.com/rate-limits) explains how the per-minute counting works, and [concurrent requests in Python](https://perceptlayer.com/blog/concurrent-api-requests-python-rate-limit) covers the parallel case. For Python error handling in general, see [handling API errors](https://perceptlayer.com/blog/handle-web-data-api-errors-python).

## How do you stretch a budget without lowering the cap?

Reduce repeated work rather than the number of runs. If an hourly agent asks the same query every time and the answer is unlikely to change within the hour, a local cache with a sensible TTL answers repeats without making a call, so they spend no credits; [caching API responses in Python](https://perceptlayer.com/blog/cache-web-data-api-responses) shows the TTL choices. Next, spend the allowance where it matters: let the agent plan its queries first, run the one or two that answer the question, and skip the rest.

Last, match frequency to how fast the data changes. Search results for a stable topic don't need an hourly check. Moving from hourly to every 6 hours on the free credits takes the allowance from 1 call to 8 per run. For a worked example of an agent that combines several searches, read the [market research agent](https://perceptlayer.com/blog/market-research-agent-search-trends).

To try the numbers on your own schedule, [get a free API key](https://perceptlayer.com/signup), paste in the ledger above and set `max_calls` from the table.

## APIs in this post

-   [Google Search **1 credit per call** · 7 endpoints](https://perceptlayer.com/apis/google-search)

## Frequently asked questions

### How many credits does a scheduled agent use per month?

Runs per month multiplied by calls per run, times the credits per call. Hourly runs in a 31-day month are 744; at 3 calls of 1 credit each, that is 744 × 3 = 2,232 credits, which is above the 1,000 free credits. The same agent on a daily schedule uses 31 × 3 = 93 credits.

### Do failed calls count against the agent's budget?

Not in credits. A failed call costs nothing and returns `X-Credits-Used: 0`. The ledger above still counts every attempt against the per-run call cap, which keeps a run from looping on errors. The one exception worth recording is a client timeout, which may have been billed even though you saw no reply.

### What happens when the agent runs out of credits?

The service is prepaid with no automatic top-up. When the balance reaches zero, calls return `insufficient_credits` with HTTP 402 and nothing more is debited. Your agent should treat that as a stop signal, not a retry case. Buy a pack or wait for the monthly free credits, then let the next run proceed.

### Can I read the remaining balance without making a billable call?

Every response carries `X-Credits-Remaining`, including errors, so the balance arrives with the calls you already make. The `search_endpoints` and `get_endpoint` MCP tools only look up the catalogue and cost nothing. For your balance, rely on the header from your last call.

ai agents credits budgeting scheduling python google search

## More from the blog

1.  [AI agents](https://perceptlayer.com/blog/category/ai-agents) 22 Jul 2026 Perceptlayer 11 min read
    
    ## [LangChain Custom Tool for Live Web Data: Python Tutorial](https://perceptlayer.com/blog/langchain-custom-tool-web-data)
    
    Build a LangChain tool in Python for Google Search and News: typed arguments, retries on 429, 502 and 503 only, and a per-run credit cap.
    
2.  [AI agents](https://perceptlayer.com/blog/category/ai-agents) 15 Jul 2026 Perceptlayer 7 min read
    
    ## [AI Agent API Cost Control: Cap Web Data Spend](https://perceptlayer.com/blog/ai-agent-web-data-costs)
    
    Cap an agent's web data spend per run: credit arithmetic for 8 and 20 calls per task, a Python budget wrapper, and why failed calls cost 0 credits.
    
3.  [AI agents](https://perceptlayer.com/blog/category/ai-agents) 23 Oct 2026 Perceptlayer 8 min read
    
    ## [Build a Market Research Agent with Search and Trends Data](https://perceptlayer.com/blog/market-research-agent-search-trends)
    
    Wire Google Search, Trends and News into one Python agent, cap it at 8 credits per question, and see a recorded Trends response.
