Intent API
The Intent API parses a free-form swap request into a structured
ParsedIntent that the Ophis frontend, or your own app or agent, can
use to pre-fill a swap form or construct a deep link.
It is a text parser, separate from the MCP server, compatibility API and rebate/reward APIs. To place orders programmatically you use the standard CoW Protocol orderbook API per chain (see AI agent integration). The Intent API itself does not place, sign, or execute trades; order signing always happens in the user's wallet.
- Base URL:
https://ophis.fi - Endpoint:
POST /api/intent - Auth: none, no API key required.
- Backed by: LibertAI Qwen 3.6 27B (open-weights, on Aleph Cloud), behind a Cloudflare Pages Function proxy. The LibertAI key is held server-side and never reaches callers.
- Machine-readable spec: openapi.json
Access control
| Control | Behaviour |
|---|---|
| Origin allowlist | Requests with a non-null Origin header are checked against https://ophis.fi and https://swap.ophis.fi. Browser calls from other origins get 403 FORBIDDEN. |
| Non-browser callers | curl and server-side scripts that omit Origin entirely are allowed, subject to the rate limit. This is the path agents use. |
| Rate limit | 30 requests per IP per rolling 60-second window. Exceeding returns 429 with a Retry-After header. |
| Caching | Identical normalized text (lowercased + trimmed) within an origin bucket is served from a 5-minute edge cache. Cache hits return the header x-ophis-cache: hit and don't consume an upstream model call. The rate-limit counter still increments on a cache hit. |
The Origin check raises the bar for casual browser abuse but can be
omitted by any non-browser client. The real protections are the per-IP
rate limit and the fact that the endpoint only normalizes text, it
moves no funds.
Request
POST /api/intent with a JSON body:
{ "text": "swap 100 USDC for ETH on Base" }
| Field | Type | Constraints |
|---|---|---|
text | string | Required. 1–280 characters. The swap request in your own words. Case-insensitive. |
The model uses temperature: 0 to reduce variation. This is not a guarantee
of identical outputs across model runs or upgrades; validate every response.
Example
curl -sS https://ophis.fi/api/intent \
-H 'content-type: application/json' \
-d '{"text":"swap 100 USDC for ETH on Base"}'
Response
On success, 200 OK with a ParsedIntent:
{
"ok": true,
"data": {
"intent": "swap",
"entities": [
{ "type": "amount", "value": "100", "raw": "100", "start": 5, "end": 8 },
{ "type": "sellToken", "value": "USDC", "raw": "USDC", "start": 9, "end": 13 },
{ "type": "buyToken", "value": "ETH", "raw": "ETH", "start": 18, "end": 21 },
{ "type": "chain", "value": "base", "raw": "Base", "start": 25, "end": 29 }
]
}
}
ParsedIntent
| Field | Type | Description |
|---|---|---|
intent | "swap" | "unknown" | unknown (with an empty entities array) if the text isn't a swap request. |
entities | Entity[] | The recognised entities, in any order. |
Entity
| Field | Type | Description |
|---|---|---|
type | "sellToken" | "buyToken" | "amount" | "chain" | The kind of entity. sellToken is what you pay with; buyToken is what you want. |
value | string | Canonical form (e.g. USDC, 0.5, optimism). |
raw | string | A case-insensitive match somewhere in the input. |
start | integer | Model-proposed, in-bounds start offset (inclusive); may be inaccurate. |
end | integer | Model-proposed, in-bounds end offset (exclusive); re-anchor raw before highlighting. |
Token values use bounded symbol syntax: 2–12 letters/digits, at least one
letter, excluding common non-token words. Values must derive from raw, which
must occur in the input; exact offsets are not guaranteed. This is not contract verification;
resolve the chain/address and request a quote before building an order.
Chain values are the parser's supported lowercase slugs. This set is
separate from the app's network selector: arc is recognized, but the app blocks
an explicit chain disabled in its deployment.
ethereum arbitrum arc avalanche base bnb gnosis
ink linea optimism plasma polygon robinhood unichain
Errors
Errors return { "ok": false, "error": { "code", "message" } }:
| Status | code | When |
|---|---|---|
400 | BAD_INPUT | Missing/empty text, text over 280 chars, or an invalid JSON body. |
403 | FORBIDDEN | The Origin header is present but not on the allowlist. |
429 | RATE_LIMITED | More than 30 requests in 60s from your IP. Honour Retry-After. |
500 | UPSTREAM | Operator configuration error (the model key is unset). |
502 | UPSTREAM / INVALID_JSON | The upstream parser was unreachable, returned a non-2xx, or produced output that failed schema validation. |
504 | TIMEOUT | The upstream parser didn't respond within 5 seconds. |
The full set of error codes is
TIMEOUT | UPSTREAM | INVALID_JSON | BAD_INPUT | RATE_LIMITED | FORBIDDEN.
{ "ok": false, "error": { "code": "RATE_LIMITED", "message": "too many requests" } }
Response headers
Every response sets cache-control: no-store, x-content-type-options: nosniff,
x-frame-options: DENY, and referrer-policy: no-referrer. A cache hit
additionally sets x-ophis-cache: hit.
Trust model
Ophis is non-custodial. This endpoint does not place, sign, or execute trades, it only normalizes natural language into structured entities. Order signing always happens in the user's wallet on the frontend. To submit orders programmatically, use the CoW Protocol orderbook API directly against the relevant orderbook host.