# Qpro+ tool definitions for AI agents

Six Qpro+ routes described for four agent platforms. They're generated from the same
source as `openapi.json`, so all of them describe the same fields, limits and caveats.

| File | Platform | Format |
|---|---|---|
| `claude-tools.json` | Claude (Anthropic API, Claude Agent SDK) | `tools` array: `name`, `description`, `input_schema` |
| `openai-tools.json` | OpenAI Responses API, and frameworks that take OpenAI-style tools | function tools |
| `gemini-function-declarations.json` | Gemini API, Vertex AI | one `tools` entry with `functionDeclarations` |
| `copilot/ai-plugin.json` | Microsoft 365 Copilot declarative agents | API plugin manifest v2.4 |
| `copilot/openapi-3.0.json` | Microsoft 365 Copilot | The same spec in OpenAPI 3.0, which Copilot's toolkit requires |
| `../openapi.json` | Google ADK (`OpenAPIToolset`), Postman, code generators, MCP server generators | OpenAPI 3.1 |

The browser-only route (`/custom-labels/print`) is left out: an agent can't use it.

## These files describe the tools. Your code makes the call.

A model that picks a tool returns its name and arguments. Your code then sends the HTTP
request with your credentials. The rule for every tool:

```
tool name   fetch_markups
route       POST https://api.beta.quandosol.com/api/custom-labels/fetch-markups     (underscores become hyphens)
headers     X-API-KEY, X-API-SECRET, Content-Type: application/json
body        the tool arguments, as JSON, unchanged
```

Credentials never go in these files or in the model's context.

## Claude

```python
import json, os, requests, anthropic

TOOLS = json.load(open("claude-tools.json"))
BASE = "https://api.beta.quandosol.com/api"

def run_tool(name, args):
    r = requests.post(f"{BASE}/custom-labels/{name.replace('_', '-')}", json=args, timeout=30,
                      headers={"X-API-KEY": os.environ["QPRO_API_KEY"],
                               "X-API-SECRET": os.environ["QPRO_API_SECRET"]})
    return r.text if r.ok else f"HTTP {r.status_code}: {r.text}"

client = anthropic.Anthropic()
msgs = [{"role": "user", "content": "Check that template FG-PALLET-4x6 resolves lot L25-8814."}]
while True:
    resp = client.messages.create(model="claude-sonnet-5-5", max_tokens=2048, tools=TOOLS, messages=msgs)
    msgs.append({"role": "assistant", "content": resp.content})
    calls = [b for b in resp.content if b.type == "tool_use"]
    if not calls:
        break
    msgs.append({"role": "user", "content": [
        {"type": "tool_result", "tool_use_id": c.id, "content": run_tool(c.name, c.input)} for c in calls]})
```

Use whichever Claude model you normally call; the model name above is only an example.

## OpenAI

Pass the array as `tools` to the Responses API. For Chat Completions, wrap each entry as
`{"type": "function", "function": {name, description, parameters}}`.

## Gemini

Pass the file's object as one entry of `tools`. It uses `parametersJsonSchema`, so the full
JSON Schema, including `apiData`'s free-form keys, carries over. With Google's Agent
Development Kit, load `openapi.json` into `OpenAPIToolset` instead.

## Microsoft 365 Copilot

`copilot/ai-plugin.json` goes inside a declarative agent's app package, next to
`declarativeAgent.json` and the app `manifest.json`. Build that package with the Microsoft 365
Agents Toolkit. Replace `${{QPRO_APIKEY_REGISTRATION_ID}}` with the ID from registering the
API key in the Teams Developer Portal.

**Known gap:** Copilot's key vault sends one secret in one header. Qpro+ needs two headers
(`X-API-KEY` and `X-API-SECRET`). Until Qpro+ accepts a single credential, a Copilot agent
needs a small relay that receives one key and adds both headers.

## Safety built into the descriptions

- Every tool says each call counts against the print allowance.
- `print_node` and `print_node_pdf` tell the model to confirm with the user before printing.
  The Copilot manifest adds a confirmation card for them.
- `amount` is capped at 500 per call in these tool files (not in the API) so a model can't
  request an unbounded run. Change the cap in the generator if your customers need more.
- The PDF tools say RFID templates are refused, and point to `render_zpl`.
