# Quick Start

Sign in, issue a Personal Access Token, and make your first authenticated call

This guide takes you from "no credentials" to a working authenticated request in four steps. You
will create an account, sign in to establish a session, issue a Personal Access Token (PAT) for
programmatic use, and call a tenant-scoped endpoint with the PAT.

> **Info:** **Prerequisites:** `curl`, Node.js 20+, or Python 3.10+.

## Step 1 — Create an account

Sign up with email and password. This creates the user, provisions a default organization for them,
and sends a verification email.

**curl**

```bash
curl -X POST https://api.indexhog.com/api/auth/sign-up/email \
-H "Content-Type: application/json" \
-d '{
  "email": "you@example.com",
  "password": "your-password",
  "name": "Your Name"
}'
```

**JavaScript**

```javascript
const res = await fetch('https://api.indexhog.com/api/auth/sign-up/email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
  email: 'you@example.com',
  password: 'your-password',
  name: 'Your Name',
}),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
'https://api.indexhog.com/api/auth/sign-up/email',
json={
'email': 'you@example.com',
'password': 'your-password',
'name': 'Your Name',
},
)
data = res.json()
```

> **Tip:** **Already have an account?** Skip to Step 2 — existing users sign in directly.

## Step 2 — Sign in and capture the session cookie

Sign in via `/api/auth/sign-in/email`. The API responds with a `Set-Cookie` containing the session
cookie. Browser clients persist it automatically; CLI clients keep a cookie jar and echo it back on
each subsequent call.

**curl**

```bash
# -c saves cookies, -b sends them back on later calls
curl -X POST https://api.indexhog.com/api/auth/sign-in/email \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{
  "email": "you@example.com",
  "password": "your-password"
}'
```

**JavaScript**

```javascript
// Browser: credentials: 'include' persists the cookie automatically.
// Node: use a cookie-aware client such as `tough-cookie` to keep the cookie across requests.
const res = await fetch('https://api.indexhog.com/api/auth/sign-in/email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
  email: 'you@example.com',
  password: 'your-password',
}),
});
```

**Python**

```python
import requests

session = requests.Session() # holds cookies across requests
session.post(
'https://api.indexhog.com/api/auth/sign-in/email',
json={
'email': 'you@example.com',
'password': 'your-password',
},
)
```

The session is now live. You can already call any `/api/user/*` account route by sending the
cookie back. For headless work, continue to Step 3.

## Step 3 — Issue a Personal Access Token

Call the API key endpoint **while signed in** (cookie attached). Choose a short list of scopes —
do not ask for every permission. The full token value is returned exactly once; store it
immediately.

**curl**

```bash
curl -X POST https://api.indexhog.com/api/auth/api-key/create \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{
  "name": "CI deploy bot",
  "expiresIn": 2592000,
  "metadata": {
    "scopes": ["user:read", "projects:read", "projects:write"]
  }
}'
```

**JavaScript**

```javascript
const res = await fetch('https://api.indexhog.com/api/auth/api-key/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
  name: 'CI deploy bot',
  expiresIn: 2592000, // 30 days in seconds, omit for no expiry
  metadata: { scopes: ['user:read', 'projects:read', 'projects:write'] },
}),
});
const { data } = await res.json();
const apiKey = data.key; // shown once — store it now
```

**Python**

```python
res = session.post(
  'https://api.indexhog.com/api/auth/api-key/create',
  json={
      'name': 'CI deploy bot',
      'expiresIn': 2592000,
      'metadata': {'scopes': ['user:read', 'projects:read', 'projects:write']},
  },
)
api_key = res.json()['data']['key']  # shown once
```

Response:

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "API key created",
"data": {
  "id": "ak_abc123",
  "name": "CI deploy bot",
  "key": "tkn_AbCdEf0123456789XyZ...",
  "expiresAt": "2026-05-05T10:00:00.000Z"
}
}
```

> **Warning:** **The `key` field is only returned at creation.** After this response, the value is no longer
> retrievable. If you lose it, revoke the token and issue a new one.

## Step 4 — Call an authenticated endpoint

Send the PAT via the `X-Api-Key` header. For tenant-scoped routes (anything touching projects,
payments, or members), the URL path carries the organization id. Get yours from
`GET /api/user/organizations`.

**curl**

```bash
# Account route — no organization id required
curl https://api.indexhog.com/api/user/me \
-H "X-Api-Key: tkn_AbCdEf0123456789XyZ..."

# Tenant route — substitute the organization id you fetched earlier

ORG_ID="01HZ3K5R4X9Y2V6QF8TJ7W0CDN"
curl "https://api.indexhog.com/api/user/organizations/${ORG_ID}/projects" \
-H "X-Api-Key: tkn_AbCdEf0123456789XyZ..."
```

**JavaScript**

```javascript
const apiKey = '<your-api-key>'; // load this from a secure env var — never hardcode
const organizationId = '01HZ3K5R4X9Y2V6QF8TJ7W0CDN';

const headers = { 'X-Api-Key': apiKey };

// Account route
const me = await fetch('https://api.indexhog.com/api/user/me', { headers });

// Tenant route
const projects = await fetch(
`https://api.indexhog.com/api/user/organizations/${organizationId}/projects`,
{ headers },
);
```

**Python**

```python
import os, requests

api_key = os.environ['API_KEY']
organization_id = '01HZ3K5R4X9Y2V6QF8TJ7W0CDN'

headers = {'X-Api-Key': api_key}

me = requests.get('https://api.indexhog.com/api/user/me', headers=headers)
projects = requests.get(
f'https://api.indexhog.com/api/user/organizations/{organization_id}/projects',
headers=headers,
)
```

A successful response looks like this:

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "User fetched successfully",
"data": {
  "id": "01HZ3K5R4X9Y2V6QF8TJ7W0CDN",
  "email": "you@example.com",
  "fullName": "Your Name",
  "handle": "yourname",
  "isVerified": true,
  "verifiedAt": "2026-04-05T10:05:00.000Z",
  "isActive": true,
  "lastLoginAt": "2026-04-05T10:00:00.000Z",
  "role": "user",
  "organizationCount": 1,
  "createdAt": "2026-04-05T10:00:00.000Z",
  "updatedAt": "2026-04-05T10:00:00.000Z"
}
}
```

## When to use each credential

| Caller                              | Credential              | Header sent                        |
| ----------------------------------- | ----------------------- | ---------------------------------- |
| Browser-based UI                    | Session cookie          | `Cookie: ...` (sent automatically) |
| Server acting as a signed-in user   | Session cookie (echoed) | `Cookie: ...`                      |
| Scripts, CI, bots, integrations     | Personal Access Token   | `X-Api-Key: tkn_...`               |
| Third-party app on behalf of a user | OAuth 2.1 access token  | `Authorization: Bearer ...`        |
| Enterprise SSO sign-in              | Session via SSO flow    | `Cookie: ...`                      |

## Best Practices

- **Never ship session cookies to scripts:** Sessions are tied to a specific browser. Use a PAT for anything non-interactive.
- **Scope tokens down:** Omit `projects:write` and `api-keys:write` from read-only automations. A smaller scope list is a smaller blast radius.
- **Set `expiresIn` on CI tokens:** Rotating on a schedule limits damage if one leaks. Long-lived tokens belong only to infrastructure you trust.
- **Pin tokens to an organization when possible:** Pass `metadata: { organizationId: "..." }` at creation time to lock the token to one tenant. The API rejects any request whose URL `:organizationId` does not match the pin.
- **Fail loudly on `401` / `403`:** A `401` means the credential is bad or expired. A `403` means the credential is valid but lacks a scope or does not belong to the requested organization. Retrying will not fix either.

## Next Steps

1. **Understand the envelope:** Read [API Structure](/docs/get-started/api-structure) for the pagination and response contract.
2. **Handle errors:** Review [Error Handling](/docs/get-started/errors).
3. **Respect limits:** See [Rate Limits](/docs/get-started/rate-limits).
4. **Choose scopes intentionally:** Check [Scopes & Permissions](/docs/get-started/scopes) before you ship production tokens.
5. **Browse endpoints:** Start at the [API Keys reference](/docs/api-reference/api-keys) or the [Projects reference](/docs/api-reference/projects).