ASI · WEB SEARCH API
API documentation
Search ASI's independent, English-language web index over HTTP. Send a GET request and receive structured results as JSON.
Last updated: 11 October 2026
Overview
ASI crawls the English-language web itself and builds its own index, without relying on another search provider's results. The API gives your agents and retrieval pipelines direct access to that index.
- Base URL
https://api.asi.xynatec.com- Requests
- HTTPS
GETrequests. Responses are JSON. - Authentication
- None at the moment. No API key or account is needed.
- Pricing and SLA
- No pricing and no service-level agreement are published. All callers share the same capacity.
Semantic search, native MCP access and pay-per-query with x402 are planned and not available yet. See what's next.
Quick start
Each example searches for forest carbon and asks for five results. Run them on a server or in a terminal, not in a web page; see Browsers & CORS.
curl
curl "https://api.asi.xynatec.com/v1/search?q=forest+carbon&limit=5"
Python
Uses only the standard library. Set a User-Agent that names your application: requests with Python's default Python-urllib user agent are currently blocked with 403 before they reach the API.
import json
import urllib.parse
import urllib.request
params = urllib.parse.urlencode({"q": "forest carbon", "limit": 5})
request = urllib.request.Request(
f"https://api.asi.xynatec.com/v1/search?{params}",
headers={"User-Agent": "my-research-agent/1.0"},
)
with urllib.request.urlopen(request, timeout=10) as response:
data = json.load(response)
for result in data["results"]:
print(result["title"], result["url"])
JavaScript (Node.js 18 or later)
// search.mjs: run with `node search.mjs`
const params = new URLSearchParams({ q: "forest carbon", limit: "5" });
const response = await fetch(`https://api.asi.xynatec.com/v1/search?${params}`);
if (!response.ok) {
throw new Error(`ASI search failed with status ${response.status}`);
}
const data = await response.json();
for (const result of data.results) {
console.log(result.title, result.url);
}
Search
GET https://api.asi.xynatec.com/v1/search?q=forest+carbon&limit=10&page=2
Returns one page of results for a query. All parameters go in the query string, URL-encoded.
| Parameter | Default | Details |
|---|---|---|
| qstring | empty | The search query. Whitespace at the start and end is removed, and anything after the first 512 characters is cut off. An empty query returns 200 with an empty results list. |
| limitinteger | 10 | Results per page, from 1 to 50. Values outside that range are clamped to it. A value that is not an integer, or is too large to read, is ignored and the default applies. |
| pageinteger | 1 | The page of results to return, from 1 to 10. Values outside that range are clamped to it. A value that is not an integer, or is too large to read, is ignored and the default applies. |
Unknown parameters are ignored. If a parameter appears more than once, the last value counts. Parameter values never cause an error: the API does not answer with 400.
Pagination
Results are ranked, and page selects a slice of that ranking, starting at (page − 1) × limit. With limit=10, page 1 holds results 1 to 10 and page 2 holds results 11 to 20. Keep limit the same while you page through a query; changing it shifts the slices.
- Up to 10 pages are available per query, so at most 500 results with
limit=50. - The response has no total count. A page with fewer results than
limitis the last one, and a page past the end returns an empty list. - A request for page 11 or higher returns page 10. The
pagefield in the response always shows the page that was served.
Query syntax
Every word of a query must appear in the page's title, description or main text. Matching ignores case and uses English stemming, so forests also finds forest. The first 8 KB of each page's main text are searchable.
| Query | Finds pages that |
|---|---|
forest carbon | contain both words, in any position |
"carbon footprint" | contain the exact phrase |
python -snake | contain python but not snake |
kestrel OR falcon | contain either word |
Some characters have a special meaning: quotation marks and apostrophes, +, -, <, > or / at the start of a word, colons, parentheses, square and curly brackets, ^, *, ~, backslashes and backticks. The uppercase words AND, OR and NOT are operators. A word followed by a colon is read as a field name; unless it names an indexed field, the part after it is dropped, so note: forest searches without forest.
To search free text from users or models as plain words, convert it to lowercase and replace every character that is not a letter or digit with a space. Matching already ignores case and punctuation, so the words searched stay the same. Malformed syntax, such as an unclosed quotation mark, never causes an error: parts the API cannot read are dropped, so results can be broader than intended, or empty.
Ranking
Results are ordered by text relevance, scored with BM25. A match in the title counts three times as much as a match in the main text, and a match in the description one and a half times as much. No other signals, such as links, popularity or publication date, are used at the moment.
The index contains pages detected as English. It changes as the crawler finds new pages and revisits known ones, so results for the same query can change over time.
Response
A successful search returns 200 with a JSON object and Content-Type: application/json.
Illustrative example. The values are not taken from the live index.
{
"query": "forest carbon",
"page": 1,
"results": [
{
"title": "How forests capture and store carbon",
"url": "https://example.org/forests-and-carbon",
"snippet": "Forests store carbon in trees, roots and soil.",
"source": "example.org",
"date": "2026-09-18"
},
{
"title": "Measuring carbon in managed woodland",
"url": "https://www.example.com/woodland/carbon",
"snippet": "Forest inventories estimate carbon from tree height and trunk width.",
"source": "www.example.com",
"date": null
}
]
}
Response fields
| Field | Details |
|---|---|
| querystring | The query that was searched, after trimming and the 512-character cut. |
| pageinteger | The page that was served, after clamping to 1–10. |
| resultsarray | The ranked results on this page, at most limit of them. Empty if nothing matches, if the query is empty or if the page is past the end. |
Result fields
| Field | Details |
|---|---|
| titlestring | The page title. An empty string if the page has none. |
| urlstring | The address of the page. |
| snippetstring | Plain text without markup, up to 300 characters. Usually the passage that best matches the query, otherwise the page's description or the start of its text. Site owners can shorten snippets with max-snippet or turn them off with nosnippet, so it can also be shorter or empty. For pages marked up as news articles, it is only the publisher's own description. |
| sourcestring | The host name of the page, for example www.example.com. |
| datestring or null | The publication date the page declares in its structured data (datePublished) or in an article:published_time tag, as YYYY-MM-DD in UTC. null if the page declares none. It is not the date ASI crawled the page. |
Results never contain the full text of a page.
Errors and limits
Errors from the API itself are JSON objects with a short code in error. Responses from the infrastructure in front of it have no JSON body, so check the status code before parsing.
| Response | Meaning |
|---|---|
503{"error": "busy"} | More than 32 searches are running at once, across all callers. Includes Retry-After: 1. |
503{"error": "timeout"} | The search took longer than 5 seconds. Includes Retry-After: 1. |
500{"error": "failed"} | The search failed on the server. |
| 403plain text | The request was blocked before it reached the API, with the body error code: 1010. This happens with some default library user agents, such as Python's. Send a User-Agent that names your application. |
| 502, 504no JSON body | The API is restarting or briefly unreachable, for example during an update. Retry after a short wait, as for 503. |
After a 503, wait for the time in Retry-After and try again. Retry a few times at most, wait longer each time, and don't send retries in parallel: the capacity is shared with everyone else. A 500 may succeed on a second try; if it keeps failing, please let us know.
Limits
- Query length: 512 characters. Longer queries are cut, not rejected.
- Results: up to 50 per request and 10 pages per query.
- Time: 5 seconds per search.
- Capacity: 32 searches at the same time, shared by all callers.
Browsers & CORS
The API sends no CORS headers, so browsers block calls from web pages on other origins. Call it from your backend, a serverless function or your agent's runtime, and pass the results on from there.
Health check
curl https://api.asi.xynatec.com/health
GET /health returns 200 with the plain-text body ok while the API is running. It does not run a search, so it says nothing about current search capacity.
Crawler
Results come from AsiBot, ASI's own crawler. It follows robots.txt and the robots directives that site owners set, and results only ever share titles, links and snippets. The AsiBot page explains how the crawler behaves and how to opt out. Read more about our approach to crawling.
Contact
Questions about integrating ASI, or feedback on the API: service@xynatec.com.