# Quickstart

From zero to a first search and fetch in two minutes.

## 1. Get a key

Ask the Cormorant operator for a project API key (`cmr_…`). Keep it out of
source control and pass it through an environment variable:

```sh
export CORMORANT_API_KEY=cmr_...
```

## 2. Search

```sh
curl -sS https://api.cormorant.ch/v1/search \
  -H "Authorization: Bearer $CORMORANT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "Holcim annual report 2025", "max_results": 5}'
```

```python
import os

import httpx

client = httpx.Client(
    base_url="https://api.cormorant.ch",
    headers={"Authorization": f"Bearer {os.environ['CORMORANT_API_KEY']}"},
    timeout=180,  # sync calls can take up to 150 s
)

found = client.post("/v1/search", json={"query": "Holcim annual report 2025", "max_results": 5})
found.raise_for_status()
for hit in found.json()["results"]:
    print(hit["rank"], hit["title"], hit["url"])
```

```ts
const BASE = "https://api.cormorant.ch";
const headers = {
  Authorization: `Bearer ${process.env.CORMORANT_API_KEY}`,
  "Content-Type": "application/json",
};

const res = await fetch(`${BASE}/v1/search`, {
  method: "POST",
  headers,
  body: JSON.stringify({ query: "Holcim annual report 2025", max_results: 5 }),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
const { results } = await res.json();
for (const hit of results) console.log(hit.rank, hit.title, hit.url);
```

Each result has `url`, `title`, every distinct `snippets` entry and the
`engines` that found it. The response also tells you the `provider`
(`searxng` or `brave`), whether it was `cached`, and its `cost_usd`.

## 3. Fetch a page

```sh
curl -sS https://api.cormorant.ch/v1/fetch \
  -H "Authorization: Bearer $CORMORANT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.holcim.com/investors", "include_html": false}'
```

```python
page = client.post(
    "/v1/fetch", json={"url": "https://www.holcim.com/investors", "include_html": False}
).json()
if page["error"] is None:
    print(page["title"], page["tier"], len(page["text"]), "characters")
else:
    print("could not fetch:", page["error"], page["request_id"])
```

```ts
const page = await (
  await fetch(`${BASE}/v1/fetch`, {
    method: "POST",
    headers,
    body: JSON.stringify({ url: "https://www.holcim.com/investors", include_html: false }),
  })
).json();
if (page.error === null) console.log(page.title, page.tier, page.text.length);
else console.log("could not fetch:", page.error, page.request_id);
```

Always check `error`: `/v1/fetch` answers HTTP `200` for unsuccessful fetches
too (a `404` on the site is `error: "http_404"`). On success you get:

- `text` — the main text, ready for an LLM or an index;
- `title`, `content_kind` (`html`, `pdf`, `json`, …), `final_url`, `status`;
- `tier` — which tier got it, and `attempts` — everything that was tried;
- `body_url` — the exact bytes, if you need them later.

## 4. Many URLs? Use a batch

```sh
curl -sS https://api.cormorant.ch/v1/batch/fetch \
  -H "Authorization: Bearer $CORMORANT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"idempotency_key": "quickstart-1", "items": [{"url": "https://example.com"}, {"url": "https://example.org"}]}'
# → {"job_id": "…", "created": true}

curl -sS "https://api.cormorant.ch/v1/jobs/$JOB_ID?include=items" \
  -H "Authorization: Bearer $CORMORANT_API_KEY"
```

See [Sync vs batch](/docs/sync-vs-batch.md) for polling, paging and callbacks.

## Next steps

- [Fetching in depth](/docs/fetching.md) — tiers, cost controls, PDFs, exact bytes.
- [Errors & retries](/docs/errors.md) — what to retry and how.
- [Using Cormorant from AI agents](/docs/agents.md).
