# Quickstart

> First successful run: start the stack, log in as admin, create a project, generate an API key, and verify health endpoints.

- Repository: Paca-AI/paca
- GitHub: https://github.com/Paca-AI/paca
- Human docs: https://www.grok-wiki.com/public/docs/paca-ai-paca-f238b2ab3d25
- Complete Markdown: https://www.grok-wiki.com/public/docs/paca-ai-paca-f238b2ab3d25/llms-full.txt

## Source Files

- `docs/guides/getting-started.md`
- `deploy/README.md`
- `scripts/install.sh`
- `services/api/internal/transport/http/handler/health_handler.go`
- `services/api/internal/transport/http/handler/auth_handler.go`
- `services/api/internal/transport/http/handler/apikey_handler.go`

---

---
title: "Quickstart"
description: "First successful run: start the stack, log in as admin, create a project, generate an API key, and verify health endpoints."
---

Paca ships as a Docker Compose stack behind an nginx gateway on port 80. The API exposes an unauthenticated health probe at `GET /api/healthz`, seeds the first admin from `ADMIN_USERNAME` and `ADMIN_PASSWORD` on startup, and serves versioned REST routes under `/api/v1`. This guide walks through a first successful run: start the stack, sign in, create a project, mint an API key, and confirm both health and API-key authentication.

## Prerequisites

| Requirement | Notes |
|---|---|
| Docker + Docker Compose | Required for production quickstart paths |
| Linux server or local machine | Install script targets any Linux host with Docker |
| Open port 80 (default) | Override with `GATEWAY_PORT` in `.env` |

<Note>
If you have not installed Paca yet, see [Installation](/installation) for prerequisites, secret generation, and optional stack scaling (external Postgres, S3, no ai-agent).
</Note>

## Start the stack

<Tabs>
<Tab title="Install script (recommended)">

The release install script downloads `docker-compose.yml` and `nginx/gateway.conf`, prompts for configuration, generates secrets, and starts the stack.

```bash
curl -fsSL https://github.com/Paca-AI/paca/releases/latest/download/install.sh -o install.sh
bash install.sh
```

Non-interactive mode uses defaults and auto-generated secrets:

```bash
PACA_YES=1 bash <(curl -fsSL https://github.com/Paca-AI/paca/releases/latest/download/install.sh)
```

Override the install directory or release tag:

```bash
PACA_DIR=./paca PACA_VERSION=latest bash install.sh
```

When the script finishes, note the printed `PUBLIC_URL`, `ADMIN_USERNAME`, and `ADMIN_PASSWORD`. Auto-generated passwords are also written to `.env`.

</Tab>
<Tab title="Manual Docker Compose">

Download release artifacts and create `.env` with required secrets:

```bash
mkdir -p paca/nginx && cd paca
curl -fsSL https://github.com/Paca-AI/paca/releases/latest/download/docker-compose.yml -o docker-compose.yml
curl -fsSL https://github.com/Paca-AI/paca/releases/latest/download/gateway.conf -o nginx/gateway.conf

cat > .env <<'EOF'
JWT_SECRET=<run: openssl rand -hex 32>
ADMIN_PASSWORD=<your-admin-password>
POSTGRES_PASSWORD=<run: openssl rand -hex 32>
AGENT_API_KEY=<run: openssl rand -hex 32>
INTERNAL_API_KEY=<run: openssl rand -hex 32>
ENCRYPTION_KEY=<run: openssl rand -hex 32>
PUBLIC_URL=http://localhost
EOF

docker compose --env-file .env up -d
```

</Tab>
</Tabs>

<Steps>
<Step title="Confirm containers are running">

```bash
docker compose --env-file .env ps
```

All services should report `running` or `healthy`. The stack may take up to a minute to pass health checks after first start.

</Step>
</Steps>

## Verify health endpoints

The nginx gateway forwards `/api/` to the API service unchanged. Health checks do not require authentication.

| Endpoint | Auth | Expected response |
|---|---|---|
| `GET /api/healthz` (via gateway) | None | HTTP 200, `data.status` is `"ok"` |
| `GET /api/healthz` (API direct, dev) | None | HTTP 200 on port 8080 |

<RequestExample>

```bash
curl -s http://localhost/api/healthz
```

</RequestExample>

<ResponseExample>

```json
{
  "success": true,
  "data": {
    "status": "ok"
  }
}
```

</ResponseExample>

<Tip>
Docker Compose health checks in the bundled stacks probe `http://127.0.0.1:8080/api/healthz` on the API container and `http://127.0.0.1/api/healthz` on the gateway. A passing gateway health check confirms nginx can reach a healthy API.
</Tip>

## Log in as admin

On first API startup, Paca seeds an admin account from `ADMIN_USERNAME` and `ADMIN_PASSWORD` if no user with that username exists. The seeded user receives the `SUPER_ADMIN` global role, which includes `projects.create` and full platform permissions. If the account already exists, credentials are left unchanged.

Default install values:

| Variable | Default |
|---|---|
| `ADMIN_USERNAME` | `admin` |
| `ADMIN_PASSWORD` | Value you set in `.env`, or auto-generated by `install.sh` |

<Warning>
Login passwords must be at least 8 characters (`binding:"min=8"` on the login request).
</Warning>

<Tabs>
<Tab title="Web UI">

<Steps>
<Step title="Open the login page">

Navigate to your `PUBLIC_URL` (for example `http://localhost`). The root route (`/`) renders the login form.

</Step>

<Step title="Sign in">

Enter the admin username and password from `.env`. On success, the app redirects to `/home` and sets HttpOnly `access_token` and `refresh_token` cookies. Token values are not returned in the response body.

</Step>
</Steps>

</Tab>
<Tab title="REST API">

:::endpoint POST /api/v1/auth/login
Authenticate with username and password. Sets `access_token` and `refresh_token` HttpOnly cookies on success.
:::

<ParamField body="username" type="string" required>
Admin username from `ADMIN_USERNAME`.
</ParamField>

<ParamField body="password" type="string" required>
Admin password from `ADMIN_PASSWORD`. Minimum 8 characters.
</ParamField>

<ParamField body="remember_me" type="boolean">
Optional. Controls refresh-token TTL for persistent sessions.
</ParamField>

<RequestExample>

```bash
curl -s -c cookies.txt -X POST http://localhost/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"YOUR_ADMIN_PASSWORD"}'
```

</RequestExample>

<ResponseExample>

```json
{
  "success": true,
  "data": {
    "message": "logged in"
  }
}
```

</ResponseExample>

</Tab>
</Tabs>

## Create a project

Project creation requires the `projects.create` global permission. The seeded `SUPER_ADMIN` admin satisfies this requirement.

<Tabs>
<Tab title="Web UI">

<Steps>
<Step title="Open the home page">

After login, go to `/home`.

</Step>

<Step title="Create a project">

Click **New project** (available from the home page or the sidebar project switcher). Fill in:

- **Name** (required)
- **Task ID prefix** (optional; auto-suggested from the name)
- **Description** (optional)
- **Public project** toggle (optional; default is private)

Submit the dialog. On success, the project appears in your project list and default backlog/timeline views are seeded server-side.

</Step>
</Steps>

</Tab>
<Tab title="REST API">

:::endpoint POST /api/v1/projects
Create a project. Requires authentication and `projects.create` permission.
:::

<ParamField body="name" type="string" required>
Project display name.
</ParamField>

<ParamField body="description" type="string">
Optional project description.
</ParamField>

<ParamField body="task_id_prefix" type="string">
Optional task ID prefix (for example `PACA`).
</ParamField>

<ParamField body="is_public" type="boolean">
When `true`, anonymous users can read the project.
</ParamField>

<RequestExample>

```bash
# Using session cookies from login:
curl -s -b cookies.txt -X POST http://localhost/api/v1/projects \
  -H "Content-Type: application/json" \
  -d '{"name":"My First Project","description":"Quickstart project"}'
```

</RequestExample>

<ResponseExample>

```json
{
  "success": true,
  "data": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "My First Project",
    "description": "Quickstart project",
    "task_id_prefix": "",
    "is_public": false,
    "settings": {},
    "created_by": "…",
    "created_at": "…"
  }
}
```

</ResponseExample>

</Tab>
</Tabs>

## Generate an API key

User API keys authenticate REST requests without session cookies. Keys are created through JWT session auth only — an existing API key cannot mint another key.

<Tabs>
<Tab title="Web UI">

<Steps>
<Step title="Open API Keys">

From the user menu, choose **API Keys**, or navigate to `/profile/api-keys`.

</Step>

<Step title="Create a key">

Click **New key**, enter a name (required, max 100 characters), and optionally set an expiry date. After creation, the full key is shown once in a reveal dialog. Copy it immediately — it cannot be retrieved again.

</Step>
</Steps>

</Tab>
<Tab title="REST API">

:::endpoint POST /api/v1/users/me/api-keys
Create an API key for the authenticated user. Requires JWT/cookie session auth (`RequireJWTAuth`); Bearer API keys are rejected.
:::

<ParamField body="name" type="string" required>
Human-readable key label. Must be non-empty and ≤ 100 characters.
</ParamField>

<ParamField body="expires_at" type="string">
Optional ISO 8601 expiry timestamp.
</ParamField>

<RequestExample>

```bash
curl -s -b cookies.txt -X POST http://localhost/api/v1/users/me/api-keys \
  -H "Content-Type: application/json" \
  -d '{"name":"Quickstart CLI key"}'
```

</RequestExample>

<ResponseExample>

```json
{
  "success": true,
  "data": {
    "id": "…",
    "name": "Quickstart CLI key",
    "key_prefix": "paca_…",
    "key": "paca_…",
    "last_used_at": null,
    "expires_at": null,
    "created_at": "…"
  }
}
```

</ResponseExample>

<Check>
The raw `key` field is returned only on creation. Subsequent `GET /users/me/api-keys` responses expose `key_prefix` but never the full secret.
</Check>

</Tab>
</Tabs>

## Verify API key authentication

Confirm the key works by calling an authenticated endpoint. The auth middleware accepts, in order: `access_token` cookie, `Authorization: Bearer` (JWT), `Authorization: ApiKey`, or `X-API-Key`.

<Steps>
<Step title="Call GET /api/v1/users/me with the API key">

<CodeGroup>

```bash title="X-API-Key header"
curl -s http://localhost/api/v1/users/me \
  -H "X-API-Key: paca_YOUR_KEY_HERE"
```

```bash title="Authorization: ApiKey header"
curl -s http://localhost/api/v1/users/me \
  -H "Authorization: ApiKey paca_YOUR_KEY_HERE"
```

</CodeGroup>

</Step>

<Step title="Confirm the response">

<ResponseExample>

```json
{
  "success": true,
  "data": {
    "id": "…",
    "username": "admin",
    "full_name": "Admin",
    "role": "SUPER_ADMIN"
  }
}
```

</ResponseExample>

A `200` response with your admin username confirms API-key auth is wired end-to-end.

</Step>

<Step title="Re-check health (optional)">

Health remains public and independent of API keys:

```bash
curl -s http://localhost/api/healthz
```

</Step>
</Steps>

## Quick reference

| Action | Path | Auth |
|---|---|---|
| Health check | `GET /api/healthz` | None |
| Login | `POST /api/v1/auth/login` | Credentials in body |
| List projects | `GET /api/v1/projects` | Cookie, Bearer JWT, or API key |
| Create project | `POST /api/v1/projects` | Cookie, Bearer JWT, or API key + `projects.create` |
| Create API key | `POST /api/v1/users/me/api-keys` | Cookie or Bearer JWT only |
| Verify API key | `GET /api/v1/users/me` | API key header |

All successful API responses use the standard envelope: `success`, `data`, and optional `request_id`. Errors return `success: false` with `error_code` and `error`.

<AccordionGroup>
<Accordion title="Stack not healthy after one minute">

Check container status and logs:

```bash
docker compose --env-file .env ps
docker compose --env-file .env logs -f api gateway
```

Confirm `GET /api/healthz` returns 200 before attempting login.

</Accordion>

<Accordion title="Login fails with 401">

Verify `ADMIN_USERNAME` and `ADMIN_PASSWORD` in `.env` match what you are entering. Passwords must be at least 8 characters. If the admin user was created on a previous run, changing `.env` does not reset the password — use the original password or create a new user through the admin API.

</Accordion>

<Accordion title="Cannot create API key with an API key">

`POST /users/me/api-keys` requires JWT session authentication. Log in through the web UI or `POST /api/v1/auth/login` first, then create keys with cookies or a Bearer access token.

</Accordion>

<Accordion title="API key returns 401">

Confirm the key starts with `paca_`, has not been revoked, and has not expired. Use `Authorization: ApiKey` or `X-API-Key`, not `Authorization: Bearer`, for API-key credentials.

</Accordion>
</AccordionGroup>

## Next

<CardGroup>
<Card title="Connect MCP server" href="/connect-mcp">
Wire `@paca-ai/paca-mcp` to Claude Desktop, VS Code, or any MCP client using the API key you just created.
</Card>
<Card title="REST API reference" href="/rest-api">
Full `/api/v1` endpoint catalog, auth modes, pagination, and response envelopes.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Health-check failures, auth cookie issues, Valkey connectivity, and MCP connection errors.
</Card>
<Card title="Local development" href="/local-development">
Contributor dev stack with hot-reload, host-side services, and the development port map.
</Card>
</CardGroup>
