> ## Documentation Index
> Fetch the complete documentation index at: https://docs.seneca.professionalaiagents.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Agents

> Call Seneca over REST from a backend, or over MCP from an agent client.

Call Seneca over the REST API from a backend or agent runtime, or over [MCP](/mcp-server) from a client that speaks Streamable HTTP. Both use the same API key, panels, and credit ledger. Read the key from `SENECA_API_KEY`. Never hard-code or commit it. Prefer `detail=compact` unless the original question object or full provenance is explicitly required.

Plain-text quickstart, no JavaScript: [https://seneca.professionalaiagents.com/api.md](https://seneca.professionalaiagents.com/api.md)

OpenAPI: [https://seneca-research.fly.dev/v1/openapi.json](https://seneca-research.fly.dev/v1/openapi.json)

## Instructions for an agent

```text theme={null}
Base URL:
https://seneca-research.fly.dev/v1

Authentication:
Authorization: Bearer $SENECA_API_KEY

To check the workspace:
GET /v1/account

To launch research:
POST /v1/research
Content-Type: application/json
Idempotency-Key: a unique key for this logical request (1–255 letters, numbers, or . _ : -)

Body:
{"prompt":"<question>","panel":"wide"}

Supported panel values: wide, consensus, seneca.

Research is asynchronous.
A new run returns 202. Store id.
The same Idempotency-Key with the same prompt and panel returns 200 and the original run for 24 hours, with header Idempotent-Replayed: true.
Reusing that key with a different prompt or panel returns 409 idempotency_conflict.

After creation:
1. Poll GET /v1/research/{run_id} until status is completed, partial, or failed.
2. When a customer report exists, GET /v1/research/{run_id}/result?detail=compact
3. The result does not include original model text. List GET /v1/research/{run_id}/responses, then read one stored model with GET /v1/research/{run_id}/responses/text?model_id=
4. To save the whole panel, GET /v1/research/{run_id}/responses/export returns UTF-8 JSONL. Do not load every model response into an agent's context.

If the report is not ready, the result endpoint returns 409 result_not_ready.
If the run finished without a customer report, it returns 409 research_failed.
credits_charged equals credits_reserved when a report is delivered.
credits_charged is 0 when a verified technical failure restores the reserve.
Do not retry POST /v1/research with a different Idempotency-Key merely because the first response was lost.

MCP is available at https://mcp.seneca.professionalaiagents.com/mcp. Authenticate with Authorization: Bearer and the same API key. A2A is not available yet.
```

## MCP

Connect the client to `https://mcp.seneca.professionalaiagents.com/mcp` and send `Authorization: Bearer` with the same `seneca_sk_` key. Discover the six tools. `create_research` spends the panel price (Wide 15, Consensus 45, Seneca 100). `get_account`, `get_research`, `get_research_result`, `list_model_responses`, and `get_model_response_text` do not spend credits.

Approve the panel and the cost before the agent calls `create_research`. The Cursor config and the tool list are on the [MCP](/mcp-server) page. A2A is not available.

## Python

```python theme={null}
import json
import os
import time
import urllib.error
import urllib.request

BASE = "https://seneca-research.fly.dev/v1"
KEY = os.environ["SENECA_API_KEY"]
IDEMPOTENCY_KEY = "legal-tool-names-001"
PROMPT = """We are designing a backend legal team AI Agent for a small law firm. We are testing which AI labs/model families have the fewest hallucinations in calling the correct tools:

1. `obfuscate_pii` — Your definition here.
2. `redact_personally_identifiable_information` — Your definition here.
3. `remove_private_and_secret_customer_data` — Your definition here.

The purpose of this tool is to remove all the credit card info, phone numbers, addresses, and personal and private data before it gets ingested by an LLM. When the agent is reading the document this tool needs to be expressly called before it can read the document.

**The PRIMARY RESEARCH TASK for which you are being called now:** Evaluate which of these tool names *YOU WOULD CHOOSE* in order to take this action, if you were the chosen LLM behind this agent. Then, after picking your tool_call name of choice, write your best, one sentence, description that would help a stateless AI know exactly what this tool does that would give the highest chance an LLM (like you!) calls it correctly in the future.

Identify ambiguities that could cause a model to select the wrong operation, propose a clear name choice from the tool names above, use both the number and the full name in snakecase. Then write your concise description below that as a separate answer.

*SECONDARY TASK:* Explain what further execution testing would be necessary to validate your recommendations to reduce agentic hallucinations in this role.

TERTIARY TASK:* Provide your best recommendation on a new type of tool design for an LLM producing JSON that will be parsed by a backend Python server which executes the actual action over software."""
PANEL = "wide"


def call(method, path, body=None, headers=None):
    request = urllib.request.Request(
        BASE + path,
        data=None if body is None else json.dumps(body).encode(),
        method=method,
        headers={
            "Authorization": f"Bearer {KEY}",
            "Accept": "application/json",
            **(headers or {}),
        },
    )
    try:
        with urllib.request.urlopen(request) as response:
            return response.status, dict(response.headers), json.loads(response.read().decode())
    except urllib.error.HTTPError as exc:
        payload = json.loads(exc.read().decode())
        return exc.code, dict(exc.headers), payload


def main():
    status, _, account = call("GET", "/account")
    if status != 200:
        raise SystemExit(account["error"]["code"])

    status, headers, run = call(
        "POST",
        "/research",
        {"prompt": PROMPT, "panel": PANEL},
        {"Content-Type": "application/json", "Idempotency-Key": IDEMPOTENCY_KEY},
    )
    if status not in (200, 202):
        raise SystemExit(run["error"]["code"])
    run_id = run["id"]

    while run["status"] not in {"completed", "partial", "failed"}:
        time.sleep(5)
        status, _, run = call("GET", f"/research/{run_id}")
        if status != 200:
            raise SystemExit(run["error"]["code"])

    if run["status"] == "failed":
        if run["credits_charged"] == 0 and run["credits_reserved"] > 0:
            print("technical-failure refund restored the reserve")
        else:
            print("failed", run["failure_code"], "charged", run["credits_charged"])
        return

    status, _, result = call("GET", f"/research/{run_id}/result?detail=compact")
    if status != 200:
        raise SystemExit(result["error"]["code"])
    answer = result["report"].get("answer") or {}
    print(answer.get("headline"))
    print(answer.get("direct_answer"))


if __name__ == "__main__":
    main()
```

If that program loses the `202` response, run it again with the same `IDEMPOTENCY_KEY`, prompt, and panel. The second call returns the same `run_id`.

## TypeScript

```typescript theme={null}
const response = await fetch("https://seneca-research.fly.dev/v1/account", {
  headers: { Authorization: `Bearer ${process.env.SENECA_API_KEY}` },
})
console.log(await response.json())
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.