---
name: matrix-web-research
description: Use Aurelio Matrix to run end-to-end web research tasks and retrieve their results.
---

# Matrix web research API

Use Matrix to perform web research for the user: submit a research task, check its status, and retrieve and present the result. Follow the user's request; do not modify their application or build an integration unless they ask. Do not invent response fields or capabilities, or initiate paid operations without authorization.

## API and Console

- **API:** `https://api.aurelioterminal.com`.
- **Console:** `https://console.aurelioterminal.com`. The Console is separate from the API and must not be used as the API base URL.

The production rollout and API contract must be confirmed before relying on the service being live. Allow `MATRIX_API_BASE` to override the API base URL when needed.

## Authentication and account boundaries

Use a Matrix API key from the Console with `Authorization: Bearer <MATRIX_API_KEY>`. Access it through a secure environment variable or secret manager. Do not hard-code, print, commit, or place the key in browser client code. Matrix keys can access the account's jobs, credits, and artifacts, but cannot manage keys or billing.

If the user needs a key, direct them to the Aurelio Console at `https://console.aurelioterminal.com`:

1. Select **Sign in** or **Create an account** and complete authentication.
2. Open **API keys** in the navigation, then select **Create key**.
3. Copy and securely save the full key from the **Save your API key** dialog; it is shown only once. Select **I've saved my key** after saving it.
4. Make the key available to the agent through a secure environment variable such as `MATRIX_API_KEY` or a secret manager. Do not ask the user to paste the secret into chat.

If the Console is unavailable, use the environment's configured Console URL or ask the user for it; do not invent another key-creation process.

Manage keys and purchase credits in the Console; a Matrix API key cannot perform those actions. The backend derives the account from the credential; never send a user ID to select an account.

## Research jobs

- **Submit:** `POST /v1/jobs` with JSON `{ "query": "...", "budget_units": 200 }`, `Content-Type: application/json`, and an `Idempotency-Key` containing a UUID. The integer budget is the maximum authorized in USD cents. Reuse the same idempotency key **and payload** after an uncertain submission; use a new key for a deliberate new task.
- **Check:** `GET /v1/jobs/:id` returns job metadata in `data`. Save the job ID and poll with a bounded interval and timeout. A polling timeout does not cancel the job or mean submission failed: check the same job ID later rather than submitting a duplicate. Status is `queued`, `running`, `completed`, or `failed`. On failure inspect `failure_code` and `failure_message`. `budget_exhausted` means the authorized budget was reached before the job could complete; a failed job may have no result artifact.
- **Retrieve output:** completed-job metadata contains `artifacts`. It is not itself the research output. Find the returned result artifact—for example, an `object_key` ending in `/result.json`—and request `GET /v1/jobs/:jobId/artifacts/:artifactId`, URL-encoding both IDs and sending the same bearer credential. The response is the **raw artifact**, not a `{ "data": ... }` envelope. Internal execution archives have no user-facing download route. Preserve and present source references when returned.
- **List history:** `GET /v1/jobs?limit=20`. Limit defaults to 20 and is between 1 and 100. `data.jobs` contains the authenticated account's jobs; `data.next_cursor` is `null` or an opaque cursor for the next page. Jobs are ordered by `created_at DESC, id DESC`. Job timestamps are Unix seconds UTC. Refresh the first page for newly submitted jobs.

JSON API success responses use `{ "data": ... }`; errors use `{ "error": { "code", "message", "request_id" } }`. Handle errors by code, surface a safe message and request ID when useful, and do not log credentials.

For complex WideSearch benchmark tasks, Aurelio estimates an average cost of about $0.42 per task and a mean completion time of five minutes, with observed times ranging from 30 seconds to 30 minutes. These are benchmark estimates, not guarantees or fixed prices. Choose an appropriate polling timeout and resume checking the same job if it expires.

### Python example

Set an explicit application user agent. This example submits a potentially billable research task; run it only with authorization. It uses Python's standard library and prints the raw result artifact.

```python
import json
import os
import time
import urllib.parse
import urllib.request
import uuid

base = os.environ.get(
    "MATRIX_API_BASE",
    "https://api.aurelioterminal.com",
).rstrip("/")
api_key = os.environ["MATRIX_API_KEY"]
headers = {
    "Authorization": f"Bearer {api_key}",
    "User-Agent": "Matrix-API-Example/1.0",
}

# Keep this value and the payload if retrying an uncertain submission.
idempotency_key = str(uuid.uuid4())
payload = {
    "query": "Example research task",
    "budget_units": 200,
}
request = urllib.request.Request(
    f"{base}/v1/jobs",
    data=json.dumps(payload).encode("utf-8"),
    headers={**headers, "Content-Type": "application/json", "Idempotency-Key": idempotency_key},
    method="POST",
)
with urllib.request.urlopen(request) as response:
    job_id = json.load(response)["data"]["id"]

job_url = f"{base}/v1/jobs/{urllib.parse.quote(job_id, safe='')}"
for _ in range(100):  # Stop after approximately five minutes.
    with urllib.request.urlopen(urllib.request.Request(job_url, headers=headers)) as response:
        job = json.load(response)["data"]
    if job["status"] in ("completed", "failed"):
        break
    time.sleep(3)
else:
    raise TimeoutError(f"Job {job_id} did not finish within five minutes; check this job again later")

if job["status"] == "failed":
    raise RuntimeError(job.get("failure_message") or "Research job failed")

artifacts = job.get("artifacts") or []
artifact = next(
    (a for a in artifacts if (a.get("object_key") or a.get("filename") or a.get("name") or "").split("/")[-1] == "result.json"),
    artifacts[0] if len(artifacts) == 1 else None,
)
if not artifact:
    raise RuntimeError("No result artifact available")
artifact_id = artifact.get("id") or artifact.get("artifact_id")
if not artifact_id:
    raise RuntimeError("Result artifact has no ID")
artifact_url = f"{job_url}/artifacts/{urllib.parse.quote(artifact_id, safe='')}"
with urllib.request.urlopen(urllib.request.Request(artifact_url, headers=headers)) as response:
    print(response.read().decode("utf-8"))
```

The user agent is not authentication. If a request fails, inspect the response and request ID; do not weaken security controls or retry paid submissions with a new idempotency key.

## Credits and settlement

`GET /v1/credits` returns `available_units`, `reserved_units`, and `unit: "usd_cent"`. Submission reserves the authorized budget. Job details expose `authorized_budget_units`, `actual_usage_units`, `released_units`, and `pricing_policy_version`. Usage and released values are `null` before settlement; zero is a settled amount, not pending.

Charges depend on usage; the advertised average per 1,000 tasks is not a fixed per-task price. Failed tasks can incur charges for consumed resources; unused authorized credits are released. Confirm production pricing and settlement behavior before estimating costs.

