# Egrit Compute Discovery API

Version 1.0.0-draft.5. Served by https://api.egrit.ai.

Search external AI and GPU compute across providers, services, deployments (regions) and
offerings. Every statement carries its provenance: whether the provider declared it, a third
party validated it, or it was observed, with the source and the time it was read.

**What the API never does.** It never scores, ranks by quality or recommends a provider; each
result says, per requirement field, whether it matched (`yes`), did not (`no`) or whether Egrit
holds no source for it (`unknown`). It never claims availability that a source does not
establish, and a price is always the price seen at a stated time. A statement made for a whole
service is never presented as true of one region. It never books, reserves, resells or runs
compute.

**What you must never send.** Requirements only: accelerator, quantity, geography, duration,
workload class, orchestration, network or storage needs, budget. Never prompts, datasets,
model contents or weights, inference inputs or outputs, workload payloads, secrets or
credentials.

Try it: the interactive documentation is at `/docs` on any Egrit API server, and this document at
`/v1/openapi.yaml` (or `/v1/openapi.json`). Every example is real data and is returned as shown when
the operation is run with its own example parameters.

**Text search.** `text` narrows a search or the provider list to rows whose public catalogue
fields (provider, service, region, city, accelerators and their aliases, the provider's printed
terms, capabilities, provisioning routes) match every term. It filters and never ranks: the
published order stands. A word of `q` that Egrit cannot read as a requirement but that is,
entire, a provider, service, region or city is used as text, and `requirement.text` says so.

**Signals.** Two routes write (#273): what a client did (`POST /v1/events`) and whether a provider
can be used at the user's organisation (`POST /v1/feedback/approval`). Their bodies are closed sets
of named fields, enums, counts and ids: no field carries text anyone typed. No cookie, no person;
kept 13 months. They need no sign-in.

**Suggestions.** One more route writes (#313, D45): a developer suggests a provider Egrit does
not list, or another source for one it does (`POST /v1/feedback/source`). A suggestion is a
pointer, never a fact: it goes to a review queue, is never shown in search or used as catalogue
data, and changes no grade. Its URL is stored and never fetched on submission. It needs no
sign-in and is kept 13 months.

**Who may read.** Every read needs a person (D44): a key in `X-Egrit-Key`, made after signing in
with GitHub or Google at https://app.egrit.ai and confirmed with a passkey, or that page's own
session. Rate limits are per person.

**Browsers.** Any client may call the API. Pages served from another web origin may call it from
a browser only from Egrit's own discovery page (CORS allows `https://app.egrit.ai` with its
session cookie, GET for reads; the signals are sent without credentials).

Status: draft. The server is not live yet (decisions P73 and D38); the rate limits are
decision P77.

The contract: https://api.egrit.ai/v1/openapi.yaml (YAML) and https://api.egrit.ai/v1/openapi.json (JSON). Interactive documentation: https://api.egrit.ai/docs.

## Operations

### GET /v1/compute/search

`searchCompute`: Search compute for a requirement

Give the requirement in structured parameters, in plain words (`q`), or both; structured
parameters win where they overlap. The response echoes the requirement as Egrit read it,
and lists anything it could not read in `unrecognised`, so nothing is silently dropped.

Results are ordered by the number of `no` verdicts, then the number of `unknown` verdicts,
then provider and deployment id. This is a sort rule, not a score.

The scope rule: a deployment's accelerator verdict is `yes` only when the provider states
the accelerator for that deployment. An accelerator stated only for the whole service is
listed under `inherited_offerings` and the verdict is `unknown`.

Prices follow the same line (decision D46): a price the provider states for the whole service,
or for a region the deployment's own cited location is in, is the row's `inherited_price`, kept
apart from its own `price`. The budget verdict may rest on it, and then says so and cites both
the price page and the location page.

Each row carries a `status` (decision D76): `fails` when a hard field (`requirement.must`) is
`no`, else `gap` when a hard field is `unknown` or a carried constraint was stated, else `meets`.
It is derived from the verdicts alone and does not change the order. `approval` is a separate
answer: whether the user's organisation permits the provider is unknown to Egrit, and the Risk
Request is the route. `meets` never means "verified", "approved" or "safe".

| Parameter | In | Type | Description |
|---|---|---|---|
| `q` | query | string | The requirement in plain words, for example "8 H200 in the EU for 30 days". A limit on where the data may be ("cannot leave the EEA", "must stay in the UK", "EEA only") narrows the places read to those within it, or is the place searched when none is. Requirements only; never prompts, data, model contents or credentials. Not stored. |
| `text` | query | string | Search terms matched against every public catalogue field, separated by spaces, each kept whole (`EU-SOUTH-03B` is one term) and the last word of each a prefix. A row must match every term; a filter, never a rank. For example `coreweave barcelona` or `infiniband`. Not stored. |
| `accelerator` | query | list of string | Accelerator ids from `/v1/accelerators`, or names as printed (H200, GB200 NVL72). Several mean any of them. A name Egrit does not know is returned in `requirement.unrecognised`. |
| `quantity` | query | integer | How many accelerators in total. |
| `country` | query | list of CountryCode | ISO 3166-1 alpha-2 country codes; any of them matches. |
| `region_group` | query | list of RegionGroup | Geographic groups (Egrit reference data, see `/v1/locations`); any of them matches. |
| `duration_days` | query | number | How long the compute is needed, in days. |
| `start_date` | query | string | When the compute is needed from. |
| `end_date` | query | string |  |
| `workload` | query | WorkloadClass |  |
| `service_kind` | query | ServiceKind |  |
| `orchestration` | query | list of string | Required software or orchestration, as capability keys (kubernetes, slurm). |
| `network_storage` | query | list of string | Material network or storage needs, as capability keys (infiniband, shared_filesystem). |
| `max_price` | query | number | A budget per `price_unit`, in `currency`. Compared only with observed prices in the same currency and unit, except that a budget per GPU-hour is also compared with a price per node-hour divided by the GPU count printed with it (decision D49); otherwise the budget verdict is `unknown` with the reason. |
| `currency` | query | CurrencyCode |  |
| `price_unit` | query | PriceUnit |  |
| `must` | query | list of string | The fields that are hard constraints (decision D76), each one stated in this requirement; `none` alone makes every field a preference. Without it, accelerator, quantity and geography are hard when stated. |
| `constraint` | query | list of string | A limit search does not match yet, as `name:value` (for example `data_class:confidential`, `model:llama`). Carried and echoed in `requirement.constraints`, never used to filter or order; a stated one makes each row's status at best `gap`. The values given for a name replace those read from `q` for it. Descriptions only; never data, prompts or credentials. |
| `cursor` | query | string | The `next_cursor` of the previous page. Opaque. |
| `limit` | query | integer | Items per page. |

### GET /v1/providers

`listProviders`: List providers

| Parameter | In | Type | Description |
|---|---|---|---|
| `catalogue_status` | query | CatalogueStatus | `listed` means on Egrit's list with nothing compiled, which says nothing about the provider; `compiled` means offering rows exist; `profiled` means a graded profile exists. |
| `text` | query | string | Search terms matched against every public catalogue field, separated by spaces, each kept whole (`EU-SOUTH-03B` is one term) and the last word of each a prefix. A row must match every term; a filter, never a rank. For example `coreweave barcelona` or `infiniband`. Not stored. |
| `cursor` | query | string | The `next_cursor` of the previous page. Opaque. |
| `limit` | query | integer | Items per page. |

### GET /v1/providers/{provider_id}

`getProvider`: One provider, its services and its profile state

| Parameter | In | Type | Description |
|---|---|---|---|
| `provider_id` | path | Slug |  |

### GET /v1/services/{service_id}

`getService`: One service, its deployments and its service-level offerings

| Parameter | In | Type | Description |
|---|---|---|---|
| `service_id` | path | Slug |  |

### GET /v1/deployments/{deployment_id}

`getDeployment`: One deployment (a region, or a tier within one), its own offerings and those of its service

| Parameter | In | Type | Description |
|---|---|---|---|
| `deployment_id` | path | Slug |  |

### GET /v1/offerings/{offering_id}

`getOffering`: One offering, with its latest observations

| Parameter | In | Type | Description |
|---|---|---|---|
| `offering_id` | path | OfferingId |  |

### GET /v1/offerings/{offering_id}/prices

`listOfferingPrices`: Every price observed for an offering, newest first

Append-only history. An old price is returned with its `observed_at`, never as current.

| Parameter | In | Type | Description |
|---|---|---|---|
| `offering_id` | path | OfferingId |  |
| `cursor` | query | string | The `next_cursor` of the previous page. Opaque. |
| `limit` | query | integer | Items per page. |

### GET /v1/offerings/{offering_id}/availability

`listOfferingAvailability`: Every availability state a source established for an offering or its deployment, newest first

Append-only history. Each item says whether the source named the offering itself or only its deployment. An empty list means no source establishes a state; it never means the offering is unavailable.

| Parameter | In | Type | Description |
|---|---|---|---|
| `offering_id` | path | OfferingId |  |
| `cursor` | query | string | The `next_cursor` of the previous page. Opaque. |
| `limit` | query | integer | Items per page. |

### GET /v1/accelerators

`listAccelerators`: The accelerator vocabulary

Models and the names providers print for them. No specifications without a cited source.

### GET /v1/locations

`listLocations`: Countries with at least one deployment, and the groups they belong to

Group membership (uk, eu, eea) is Egrit reference data, not a provider statement: uk is GB, eu the EU member states, eea the EU plus Iceland, Liechtenstein and Norway.

### GET /v1/sources/{source_id}

`getSource`: The source behind a statement

Where a statement came from and when it was read. The captured page itself is not republished here (decision P55).

| Parameter | In | Type | Description |
|---|---|---|---|
| `source_id` | path | SourceId |  |

### POST /v1/events

`recordEvent`: Record what a client did

A search and which requirement fields it asked (never their values), the results or no result, a result opened, a candidate chosen, a link followed. `visit` is random per page load or run and kept nowhere, so events count visits, not people. No cookie; nothing a user typed. Kept 13 months. `pack_copied` (#372) says a request pack was made for a deployment and needs `deployment_id` with its service and provider; it never carries the pack.

### POST /v1/feedback/approval

`recordApprovalAnswer`: Answer "Can you use this provider at your organisation?"

Tied to the provider, and the service and deployment where known. `wants_help` answers "Need help getting this provider approved?", asked only after no or don't know, and refused until that help is offered (#279). A client may skip both questions. Kept 13 months.

### POST /v1/feedback/source

`suggestSource`: Suggest a missing provider or another source

`kind: provider` names a provider or service Egrit does not list, with its site. `kind: resource` points at another source for a provider, service or deployment Egrit lists (a pricing page, a catalogue or availability API, a status page, a region page, a certification page, documentation, an SDK or repository), with the listed ids and, optionally, the fields it answers that Egrit shows as `unknown`. The suggestion is queued for review with the channel it came from; one that repeats a listed provider, a source already captured or an open suggestion is linked to it and not queued again. Nothing from it appears in search or changes a grade until a person has compiled and accepted it. The URL is stored and never fetched on submission. Search text is never taken: the search it relates to is sent, if at all, as its structured requirement. `note` and `contact` are the only free text; send nothing confidential, and a contact only if you want to be told the outcome. Kept 13 months.

## Connect an agent (MCP)

A read-only MCP server calls this API; each tool is one operation above.

- `search_compute`: searchCompute
- `get_provider`: getProvider
- `get_service`: getService
- `get_deployment`: getDeployment
- `get_offering`: getOffering
- `get_evidence`: getSource
- `get_request_pack`: getDeployment
- `suggest_source`: suggestSource

### By URL, with nothing installed

The same tools, read-only, over MCP's Streamable HTTP transport: `POST https://api.egrit.ai/mcp`.

Claude Code:

```bash
claude mcp add --transport http egrit https://api.egrit.ai/mcp
```

VS Code: open https://api.egrit.ai/docs/connect/vscode-url to add it in one click, or put this in `.vscode/mcp.json`:

```json
{
  "servers": {
    "egrit": {
      "type": "http",
      "url": "https://api.egrit.ai/mcp"
    }
  }
}
```

ChatGPT:

```
To add Egrit's Compute Discovery to ChatGPT (from OpenAI's "Connect and test your plugin", retrieved 2026-09-28; developer mode depends on account and workspace policy):

1. In ChatGPT, open Settings, then Security and login, and turn on Developer mode.
2. Go to https://chatgpt.com/plugins and select the plus button.
3. Enter a name (Egrit Compute Discovery) and a description.
4. Under Connection, enter the MCP server URL: https://api.egrit.ai/mcp
5. Create the connection and review the six tools it lists. All are read-only; no sign-in is needed.

Send requirements only: never prompts, datasets, model contents, credentials or secrets.
```

### With the `egrit` command

After `pip install ./clients/python` from a checkout of the Egrit repository, `egrit mcp` runs the server on stdio. VS Code: open https://api.egrit.ai/docs/connect/vscode to add it in one click.

### From a checkout of the repository

`.mcp.json` at the root of the checkout (Claude Code and other clients):

```json
{
  "mcpServers": {
    "egrit": {
      "command": "python3",
      "args": [
        "app/egrit/compute/mcp.py"
      ],
      "env": {
        "EGRIT_API_URL": "https://api.egrit.ai"
      }
    }
  }
}
```

Claude Code, from the root of the checkout:

```bash
claude mcp add --transport stdio --scope project --env EGRIT_API_URL=https://api.egrit.ai egrit -- python3 app/egrit/compute/mcp.py
```

VS Code, `.vscode/mcp.json`:

```json
{
  "servers": {
    "egrit": {
      "type": "stdio",
      "command": "python3",
      "args": [
        "${workspaceFolder}/app/egrit/compute/mcp.py"
      ],
      "env": {
        "EGRIT_API_URL": "https://api.egrit.ai"
      }
    }
  }
}
```

Cursor, `.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "egrit": {
      "command": "python3",
      "args": [
        "app/egrit/compute/mcp.py"
      ],
      "env": {
        "EGRIT_API_URL": "https://api.egrit.ai"
      }
    }
  }
}
```

## Prompt for any AI assistant

Connect to Egrit's Compute Discovery MCP server with the configuration below, then list its tools and read their descriptions before you use them.

Egrit answers one question: where does published GPU and AI compute match a requirement. The server is read-only: it cannot provision, reserve, buy or change anything. Use it like this:

- Send requirements only: accelerator, quantity, location, dates or duration, workload class, service kind. Never send prompts, datasets, model contents, source code, credentials or secrets, and never paste my other conversations into a search.
- Report each verdict (yes, no, unknown) with its evidence: the source id, its URL and when it was retrieved. Use `get_evidence` to open a source.
- `unknown` means Egrit holds no source for that field. It says nothing about the provider; do not report it as a no.
- A match is not a recommendation. Egrit does not say a provider is good, safe, compliant or approved, and shows a price or availability only where an observation is dated.
- If the search lists words it did not understand (`requirement.unrecognised`), tell me and rephrase as structured fields rather than guessing.

The tools:
- `search_compute`: Search published compute for a requirement, in plain words (q), structured fields, or both; text filters rows by words in any public field. Returns the requirement as understood (anything not read is listed in requirement.unrecognised), then one row per deployment with a verdict per asked field and the cited evidence for each, and next_cursor for the next page.
- `get_provider`: One provider by id (for example coreweave): its catalogue status and services.
- `get_service`: One service by id: its provider, kind, deployments and offerings.
- `get_deployment`: One deployment (a region or zone) by id: its stated location, its own offerings and those it inherits from its service. To hand it to whoever approves suppliers, use get_request_pack; never restate a statement from here without its source URL and retrieved_at, and list every null or not_established field as unknown to Egrit.
- `get_offering`: One offering by id: the accelerator as stated, its scope, the latest dated price and availability where observed, and capacity statements.
- `get_evidence`: One cited source by id (the ids in every verdict's evidence): the URL, when it was retrieved and what it is.
- `get_request_pack`: The request pack for one deployment (#372): what a developer hands to whoever approves suppliers. Markdown as text and the same pack as JSON in structuredContent, identical to the CLI's `egrit deployment <id> --pack` and the Web page's. Every statement with its provenance class, grade, source URL and retrieved_at; prices with observed_at, a service's price kept apart from the deployment's own; availability only where a source establishes it; the provider's residency statement once the API serves it; every unknown listed. Give it to the user as it is: do not shorten it, drop an unknown or add a judgement.
- `suggest_source`: The one tool that writes. Suggest to Egrit a provider it does not list, or another source (a pricing page, a catalogue or availability API, a status or region page, a certification page, documentation, an SDK or repository) for one it lists, for example where a result shows unknown. Only when the user wants to: show them exactly what will be sent and get their yes before calling. A person at Egrit reviews it; nothing is published as sent, and the address is never fetched. Returns a reference and whether Egrit already had it.

Configuration (`.mcp.json` at the root of a checkout of the Egrit repository):

```json
{
  "mcpServers": {
    "egrit": {
      "command": "python3",
      "args": [
        "app/egrit/compute/mcp.py"
      ],
      "env": {
        "EGRIT_API_URL": "https://api.egrit.ai"
      }
    }
  }
}
```

Or, in Claude Code, from the root of that checkout:

```
claude mcp add --transport stdio --scope project --env EGRIT_API_URL=https://api.egrit.ai egrit -- python3 app/egrit/compute/mcp.py
```

Or connect by URL, with nothing installed: the same tools over MCP's Streamable HTTP at https://api.egrit.ai/mcp. In Claude Code:

```
claude mcp add --transport http egrit https://api.egrit.ai/mcp
```

The server needs the Egrit API running at https://api.egrit.ai. The API's contract is https://api.egrit.ai/v1/openapi.yaml, and a Markdown description of it is https://api.egrit.ai/docs.md.
