# Quickstart

Every request needs two headers: a key, and the workspace the key acts in.

<CodeTabs syncKey="lang">

```bash title="cURL"
curl https://api.genuineai.app/api/v1/agents \
  -H "X-Api-Key: gai_…" \
  -H "X-Tenant-Id: 3f2a9c1e-7b44-4c0d-9d2f-8a1b6e5c0d31"
```

```js title="JavaScript"
const headers = {
  "X-Api-Key": "gai_…",
  "X-Tenant-Id": "3f2a9c1e-7b44-4c0d-9d2f-8a1b6e5c0d31",
};

const res = await fetch("https://api.genuineai.app/api/v1/agents", { headers });
const { data } = await res.json();
```

```python title="Python"
import requests

headers = {
    "X-Api-Key": "gai_…",
    "X-Tenant-Id": "3f2a9c1e-7b44-4c0d-9d2f-8a1b6e5c0d31",
}

res = requests.get("https://api.genuineai.app/api/v1/agents", headers=headers)
data = res.json()["data"]
```

</CodeTabs>

That is the complete shape of a request. Everything below is detail, and `headers` refers
to that same pair everywhere it appears from here on.

## Get a key

Keys are created in the app, under **API keys** in your account settings. No endpoint
issues one: a credential that authenticates the API is not itself issued over the API, so
a leaked key cannot be used to mint more.

While creating a key you can narrow what it may do, set an expiry and add an IP
allowlist. [Authentication](/authentication) covers all three.

**The key is shown once.** Only a hash of it is stored, so it cannot be displayed again.
Store it when you create it, and issue a new one if you lose it.

## Verify the key

Start with `GET /api/v1/me`. It reports which user and workspace the key resolved to, and
what it may do:

<CodeTabs syncKey="lang">

```bash title="cURL"
curl https://api.genuineai.app/api/v1/me \
  -H "X-Api-Key: gai_…" -H "X-Tenant-Id: <workspace-id>"
```

```js title="JavaScript"
const me = await (await fetch("https://api.genuineai.app/api/v1/me", { headers })).json();
```

```python title="Python"
me = requests.get("https://api.genuineai.app/api/v1/me", headers=headers).json()
```

</CodeTabs>

```json
{
  "usr_id": "3f2a9c1e-7b44-4c0d-9d2f-8a1b6e5c0d31",
  "usr_email": "integrations@example.com",
  "tenant_id": "8a1b6e5c-0d31-4c0d-9d2f-7b444c0d9d2f",
  "tenant_name": "Example Co",
  "usr_permissions": ["agent:view", "thread:view", "message:create"],
  "features": ["module-api", "module-agent-editor"],
  "credits": {"available": 50000, "used": 1284, "remaining": 48716, "hasCredits": true, "low": false},
  "auth_method": "api_key"
}
```

`usr_permissions` is the effective set: roles, narrowed by your plan, then narrowed again
by the key's own scope. A permission missing here is refused everywhere else, which makes
this the fastest way to explain a 403.

API access requires the **module-api** entitlement on your plan. If your key is rejected
with `license_required`, that entitlement is what is missing.

## The two headers

| Header | |
|---|---|
| `X-Api-Key` | Your key. Begins `gai_`. |
| `X-Tenant-Id` | The workspace this request acts in. Required: a key is pinned to one workspace, and a request without this header is refused. |

## Make a read request

<CodeTabs syncKey="lang">

```bash title="cURL"
curl "https://api.genuineai.app/api/v1/files?repo_type=media&limit=10" \
  -H "X-Api-Key: gai_…" \
  -H "X-Tenant-Id: <workspace-id>"
```

```js title="JavaScript"
const res = await fetch("https://api.genuineai.app/api/v1/files?repo_type=media&limit=10", { headers });
const { data, has_more } = await res.json();
```

```python title="Python"
res = requests.get(
    "https://api.genuineai.app/api/v1/files",
    params={"repo_type": "media", "limit": 10},
    headers=headers,
)
page = res.json()
```

</CodeTabs>

`repo_type` is required because files are always listed from one repository rather than
across the workspace. `media` is the shared media library, and [organizing
assets](/organizing-assets) covers the rest. Most collections need no equivalent
parameter.

Collections accept `limit` and support filtering on their own fields, as in `GET
/api/v1/agents?agent_active=true`. Each endpoint's reference page lists the filters it
accepts.

## Error responses

Errors are [problem+json](/errors) with a stable `code` you can branch on:

```json
{
  "type": "https://docs.genuineai.app/errors/validation-failed",
  "title": "Validation failed",
  "status": 422,
  "detail": "Validation error",
  "code": "validation_failed",
  "errorId": "8f14e45f"
}
```

Branch on `code`, never on `title` or `detail`, which are prose and may be reworded.
Quote `errorId` when you need to ask us about a specific request.

## What a key cannot do

A key can never do more than the person who created it, and `key_permissions` narrows it
further. Subscriptions, key management and workspace provisioning are not part of the
API at all. They are actions a person takes in the app.

## Next steps

- [Authentication](/authentication) covers scoping keys, IP allowlists and expiry.
- [Errors](/errors) lists the full code vocabulary.
- [Rate limits](/rate-limits) explains the tiers and what a 429 tells you.
- The [API reference](/reference) describes every endpoint.
