---
title: "LLM Tool Schema Design: A Checklist for Web APIs"
description: "LLM tool schema design checklist, applied to the Google Maps simple-search endpoint: a before and after schema that cuts 28 result fields to 7."
url: https://perceptlayer.com/blog/agent-tool-schema-design-web-apis
---

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

# LLM Tool Schema Design: A Checklist for Web APIs

24 Jul 2026 Perceptlayer 7 min read

A good LLM tool schema exposes only the parameters a model should choose, names them for what they do, constrains values with enums and limits, and returns a trimmed result. Copying a web API's parameter list one-to-one gives you a schema that works but wastes tokens and invites bad calls. This checklist applies eight rules to the Google Maps `simple-search` endpoint, with a before and after schema.

## Key takeaways

-   The raw `simple-search` endpoint takes 7 parameters; a model only needs to choose 3 or 4 of them. Fix the rest in code.
-   Each recorded result item has 28 top-level fields. Keeping 7 of them cuts the two recorded items from 3,114 to 531 characters, about 83% less.
-   Put the enum, the range and the example in the schema, not in a prompt. Schema constraints travel with the tool.
-   Return structured errors and a `has_more` flag so the model can correct itself instead of guessing.
-   Every call costs 1 credit and failed calls cost nothing, so a schema that prevents a bad call saves tokens, not credits.

## What does a model need from a tool schema?

A model reads your tool's name, description and parameter schema as plain text and decides from that text alone whether to call it and with what arguments. Both [Anthropic's tool use docs](https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/overview) and the [MCP specification's tools page](https://modelcontextprotocol.io/specification/2025-06-18/server/tools) (2025) define a tool by a name, a description and a [JSON Schema](https://json-schema.org/) for the input. Those are the fields that carry your intent to the model, so ambiguity there becomes a wrong call at runtime.

The [Google Maps API](https://perceptlayer.com/apis/google-maps) is a good test case because its search endpoint has parameters of different kinds: one the model must fill, some it shouldn't touch, and a result with far more fields than a reasoning step needs.

## What does the one-to-one schema look like?

The one-to-one schema mirrors the endpoint's parameter list exactly. For `GET /v1/google-maps/simple-search` that means `query` (required) plus `hl`, `gl`, `google_domain`, `limit`, `page` and `fields`, with no descriptions. It's valid, and it's what you get from generating a tool straight from an OpenAPI file. Here's that version as a tool definition:

```json
{
  "name": "simple_search",
  "description": "Search Google Maps.",
  "input_schema": {
    "type": "object",
    "properties": {
      "query": { "type": "string" },
      "hl": { "type": "string" },
      "gl": { "type": "string" },
      "google_domain": { "type": "string" },
      "limit": { "type": "integer" },
      "page": { "type": "integer" },
      "fields": { "type": "string" }
    },
    "required": ["query"]
  }
}
```

A model has to guess what `hl` means, whether `google_domain` is worth setting, and what to put in `fields`. It will sometimes set all three to plausible but wrong values.

## The checklist

Work through these in order. Each item is a decision you make once, in the tool wrapper, rather than a rule you repeat in every prompt.

1.  **Name the tool for the task, not the endpoint.** `search_places` tells a model when to reach for it; `simple_search` doesn't. If you expose several Google Maps endpoints, name them as verbs on nouns: `search_places`, `get_business_details`, `get_reviews`.
2.  **Write the description as a decision rule.** Say what the tool returns, when to use it and what to call next. Mention that it returns a `business_id` that other tools take as input. Add the cost in credits so a cost-aware agent can plan; the endpoint costs 1 credit per successful call.
3.  **Rename parameters that only make sense to the API.** Expose `gl` as `country` and drop `hl` from the schema entirely. The reference types both only as strings, so what `country` should hold is your decision (see item 5). Your wrapper maps `country` back to `gl` when it builds the request.
4.  **Remove parameters the model shouldn't choose.** `google_domain`, `hl` and `fields` are configuration, not decisions. Set them in code. Fewer parameters means fewer ways to produce an invalid call.
5.  **Turn free strings into enums.** If your agent only serves three markets, `country` is an enum of those three values. The model can't invent a fourth. Test each value in the playground on the Google Maps page before you commit to it; the reference only types `gl` as a string and doesn't list allowed values.
6.  **Bound numbers and say what they do.** Give `limit` a `minimum`, a `maximum`, a `default` and a description. Pick the numbers yourself: the endpoint reference documents `limit` only as an integer, so any cap or default is your policy, not the API's. Put the default in the schema and use the same value in the wrapper, so the model sees what happens when it omits `limit`.
7.  **Add one example per free-text field.** `"ramen in Berlin"` in the `query` description steers the model toward place-plus-location phrasing better than a paragraph of instructions.
8.  **Trim the output before the model sees it.** Return the fields the next step needs, plus a `has_more` flag, and nothing else.

## Which fields does the model actually need?

A search result is only useful to an agent if it can pick a place and hand its ID to the next tool. The recorded `simple-search` response for `query=restaurants in new york` returns items with 28 top-level fields, including `name`, `rating`, `review_count`, `business_id`, `subtypes` and `full_address`. It also carries owner IDs and links, a `photos_sample` array of photo objects, an `attributes` object and several near-duplicate address fields.

```json
{
  "name": "Altair Restaurant NYC",
  "review_count": 1225,
  "rating": 4.8,
  "website": "https://www.altairnyc.com/",
  "business_id": "0x89c2591598891521:0x4ebcde18610e64",
  "subtypes": ["New American restaurant", "Restaurant"],
  "full_address": "351 W 38th St, New York, NY 10018, United States",
  …
}
```

Seven fields cover search, ranking and follow-up: `name`, `rating`, `review_count`, `business_id`, `full_address`, `subtypes` and `website`. The rest is real data, but it belongs to a different question.

To put a number on it, we compared the first recorded item with a copy holding only those seven fields. Method: compact JSON (no whitespace) of each item, counting characters. The full item is 1,573 characters and the trimmed one 274, an 83% cut; across both recorded items it's 3,114 against 531. The recording already shortens two long URLs with `…`, so real full items are somewhat larger. Characters aren't tokens, so treat this as a size ratio, not a token bill. A ten-place result carries that overhead ten times. If a later step needs phone numbers, call [Business Details](https://perceptlayer.com/apis/google-maps/business-details) for that one place instead of carrying every result's `phone_numbers` through the whole conversation.

## What does the after schema look like?

The after schema applies all eight items: a task-named tool, a decision-rule description, three model-facing parameters plus an optional page, an enum and a bounded integer with a default. The country values below are an example; choose the markets you serve.

```json
{
  "name": "search_places",
  "description": "Find places on Google Maps from a text query. Returns up to `limit` places with name, rating, review_count, full_address, subtypes, website and business_id, plus has_more. Pass business_id to other Google Maps tools. Costs 1 credit per successful call.",
  "input_schema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "What and where, for example 'ramen in Berlin'."
      },
      "country": {
        "type": "string",
        "enum": ["US", "GB", "DE"],
        "description": "Market to search in."
      },
      "limit": {
        "type": "integer",
        "minimum": 1,
        "maximum": 10,
        "default": 5,
        "description": "Number of places to return."
      },
      "page": {
        "type": "integer",
        "description": "Only set this when the previous result had has_more true."
      }
    },
    "required": ["query"]
  }
}
```

The wrapper that runs when the model calls it does the other half of the work. Its `limit=5` matches the schema default. It maps `country` to `gl`, sends the request and projects each item down to the seven fields.

```python
import os
import requests

KEEP = ["name", "rating", "review_count", "business_id",
        "full_address", "subtypes", "website"]

def search_places(query, country=None, limit=5, page=None):
    params = {"query": query, "limit": limit}
    if country:
        params["gl"] = country
    if page is not None:
        params["page"] = page
    r = requests.get(
        "https://api.perceptlayer.com/v1/google-maps/simple-search",
        params=params,
        headers={"X-API-Key": os.environ["API_KEY"]},
        timeout=30,
    )
    body = r.json()
    if not r.ok:
        # Hand the model the code and message so it can correct the call.
        return {"error": body["error"]["code"], "message": body["error"]["message"]}
    return {
        "places": [{k: p.get(k) for k in KEEP} for p in body["items"][:limit]],
        "has_more": body["pagination"]["has_more"],
    }
```

An `invalid_parameters` (422) error, for instance, reaches the model as a short code and message, not a stack trace. See [the error reference](https://docs.perceptlayer.com/errors) for the full list of codes.

## Does a tighter schema change what you pay?

A tighter schema doesn't change the credit cost of a call. Every successful `simple-search` call costs 1 credit whatever you do with the response, and failed calls cost nothing ( [credits](https://docs.perceptlayer.com/credits)). What it changes is tokens and retries: fewer fields in the result means a smaller tool message in the model's context, and fewer free-form parameters means fewer malformed calls.

Retries are where the schema affects spend most. An agent that makes 20 searches per task spends 20 credits at 1 credit each, and a loop that repeats a search because the model misread `page` adds credits without adding answers. Our post on [capping agent data spend](https://perceptlayer.com/blog/ai-agent-web-data-costs) covers hard limits, and [MCP rate limiting](https://perceptlayer.com/blog/mcp-server-rate-limiting-agents) covers request rates.

If you'd rather skip writing wrappers for each endpoint, the [MCP server](https://docs.perceptlayer.com/ai-agents) exposes `search_endpoints`, `get_endpoint` and `call_endpoint`, so a model discovers parameters from the catalogue at run time. The trade-off is that your own curated schema, as above, constrains the model more tightly. [MCP vs function calling](https://perceptlayer.com/blog/mcp-vs-function-calling-web-data) compares the two approaches.

Next step: copy the wrapper above, then run the same query in the playground on the [Google Maps API page](https://perceptlayer.com/apis/google-maps) to check each enum value before you ship it. You can [get a free API key](https://perceptlayer.com/signup) to do it.

## APIs in this post

-   [Google Maps **1 credit per call** · 10 endpoints](https://perceptlayer.com/apis/google-maps)

## Frequently asked questions

### How many parameters should a tool schema have?

As few as the model needs to decide. For `simple-search`, the endpoint has 7 parameters, but a model only needs `query`, a market, a limit and a page. Hold the rest constant in code. Each parameter you expose is another value the model can set wrongly, and another line in every request's context.

### Should I generate tool schemas from the OpenAPI file?

Use it as a starting draft, not the final schema. A generated schema mirrors the API, which is built for programs that read documentation, not models that read descriptions. Add task-focused names, descriptions with examples and enums, and drop configuration-only parameters. The generated file is still useful for checking types and required fields.

### Where should output trimming happen?

In your wrapper, before the result goes back to the model. Trimming in code is guaranteed, whereas asking the API for a subset depends on the format of the `fields` parameter, which the reference documents only as a string. Test `fields` in the playground if you want to trim at the source as well.

### Does the model need to know the cost of a call?

It helps when the agent plans multi-step work. Stating "costs 1 credit per successful call" in the description lets a cost-aware agent budget searches. Failed calls cost nothing and return `X-Credits-Used: 0`, so the model doesn't need to worry about retrying after an error. See [credits](https://docs.perceptlayer.com/credits) for how billing works.

tool calling mcp json schema google maps ai agents

## More from the blog

1.  [AI agents](https://perceptlayer.com/blog/category/ai-agents) 12 Oct 2026 Perceptlayer 7 min read
    
    ## [MCP Server Too Many Tools: Keeping Agent Context Small](https://perceptlayer.com/blog/mcp-too-many-tools-context)
    
    One tool per endpoint for 1,659 endpoints is about 567,000 characters of schema. A 10-step checklist to keep an MCP server's tool list small.
    
2.  [AI agents](https://perceptlayer.com/blog/category/ai-agents) 16 Sept 2026 Perceptlayer 7 min read
    
    ## [MCP Tool Call Error Handling: When an Agent Should Retry](https://perceptlayer.com/blog/mcp-tool-call-errors-agent-retries)
    
    Each call\_endpoint error code mapped to one agent action (fix, wait, stop or ask the user), with the exact error text the model sees and a system prompt.
    
3.  [AI agents](https://perceptlayer.com/blog/category/ai-agents) 8 Sept 2026 Perceptlayer 10 min read
    
    ## [MCP vs Function Calling for Live Web Data in Agents](https://perceptlayer.com/blog/mcp-vs-function-calling-web-data)
    
    One Google Maps endpoint as an MCP tool and as a hand-written function: setup, where each spends tokens, upkeep, and a verdict per use case.
