# Quickstart

## Base URL

```
https://api.example.com/v1
```

## Auth

Use an API key in the `Authorization` header:

```
Authorization: Bearer <api_key>
```

## Canonical 6-Step Workflow

This is the recommended production flow:

1. Recruit group
2. Create study
3. Ask question (one at a time)
4. Poll jobs to `finished`
5. Complete study
6. Enable sharing

For async polling details and status patterns, see:
`docs/api/guides/async_job_polling.md`

For known pitfalls and corrections, see:
`docs/api/guides/common_mistakes.md`

For filter syntax, exact-match rules, and documented value sets, see:
`docs/api/guides/filters.md`

## Conventions You Must Know

### Response envelope pattern

Creation endpoints return nested resources:

- Group recruit/create responses: `response.group`
- Study create response: `response.study`

Do not assume top-level `id`/`uuid`.

### ID and UUID usage

- Most group endpoints accept numeric ID or UUID in path (`{group_id}`).
- `POST /v1/research-groups/{group_uuid}/append` requires UUID in the path.
- Study creation expects `research_group_uuid` in body.

### Question execution model

- Study questions should be sequential.
- Ask one question, poll one job to `finished`, then ask the next question.

## Step 1: Recruit a Group

```bash
curl -X POST "$BASE_URL/research-groups/recruit" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Midwest Parents",
    "description": "People who regularly cook dinner at home and manage grocery shopping for their household.",
    "filters": {
      "match": "all",
      "filters": {
        "country": { "any": ["USA"] },
        "state": { "any": ["IL", "IN", "MI", "OH"] },
        "age": { "min": 30, "max": 45 },
        "is_parent": true
      }
    },
    "group_size": 40,
    "sample_method": "random",
    "dedupe": true
  }'
```

Extract for step 2:

- `group_uuid = response.group.uuid`

Notes:

- `description` is soft-targeting context, not a strict filter predicate.
- Use `description` plus demographic filters for best targeting.
- Use 2-letter state/province codes for US/Canada (for example `IL`, `ON`).

## Step 2: Create a Study

```bash
curl -X POST "$BASE_URL/research-studies" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Grocery Preferences 2026",
    "objective": "Understand grocery shopping priorities",
    "research_group_uuid": "<group_uuid_from_step_1>"
  }'
```

Extract for steps 3-6:

- `study_id = response.study.id`

## Step 3: Ask One Study Question

```bash
curl -X POST "$BASE_URL/research-studies/<study_id>/questions" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "question": "What matters most when choosing a grocery store?"
  }'
```

Extract for step 4:

- `job_id = response.job_ids[0]` (poll one job from the batch)

## Step 4: Poll Until Finished

```bash
curl -X GET "$BASE_URL/jobs/<job_id>" \
  -H "Authorization: Bearer $API_KEY"
```

Continue only when `status` is `finished`.

## Step 5: Complete the Study

```bash
curl -X POST "$BASE_URL/research-studies/<study_id>/complete" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "force": false
  }'
```

Extract completion job and poll it to `finished`.

Notes:

- Completion runs the analysis pipeline asynchronously.
- If a completion step is already done and `force` is `false`, that step is skipped and may be omitted from `queued_steps`.
- Set `force: true` only when re-running completion analysis is intended.

## Step 6: Enable Sharing

```bash
curl -X POST "$BASE_URL/research-studies/<study_id>/share" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true
  }'
```

Notes:

- Share URLs are public while sharing is enabled.
- Treat share links like secrets.
- Optional UTM conventions:
  - `?utm_source=ce` for cold email
  - `?utm_source=blog` for content/blog links

## Optional: Upload an Attachment for Question Endpoints

Upload each image/PDF once, then reuse `media_asset.id` in question attachments.

```bash
FILE_B64=$(base64 < concept.pdf | tr -d '\n')
curl -X POST "$BASE_URL/media-assets" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"filename\": \"concept.pdf\",
    \"mime\": \"application/pdf\",
    \"file_data\": \"$FILE_B64\",
    \"encoding\": \"base64\"
  }"
```

Use returned `media_asset.id` in:

- `POST /v1/research-agents/{agent_id}/questions`
- `POST /v1/research-groups/{group_id}/questions`
- `POST /v1/research-studies/{study_id}/questions`

## Optional: Ask a Single Agent Directly (No Study/Group Required)

Requires API key scope `studies:write`.

```bash
curl -X POST "$BASE_URL/research-agents/<agent_id>/questions" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "question": "What is your first reaction to this product concept?"
  }'
```

Response includes `job_id` and `status_url` for polling.

After the job finishes, retrieve the canonical saved direct-question record via
`GET /v1/direct-questions/{question_id}` or `GET /v1/direct-questions/{question_uuid}`.

## Optional: Ask a Research Group Directly (No Study Required)

Requires API key scope `studies:write`.

```bash
curl -X POST "$BASE_URL/research-groups/<group_id_or_uuid>/questions" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "question": "What is your first reaction to this concept?"
  }'
```

Response includes `job_ids`; poll one for workflow status.

After a returned job reaches `finished`, retrieve the canonical saved
direct-question record to read normalized answers and any generated summary
artifacts.

## Optional: Retrieve a Saved Direct Question

Use this after creating a direct agent/group question and polling one returned
job to completion.

```bash
curl -X GET "$BASE_URL/direct-questions/<question_uuid>" \
  -H "Authorization: Bearer $API_KEY"
```

The saved direct-question detail response includes:

- normalized `targets[].answer` records
- any generated `artifacts` such as a summary
- saved `attachments`
- canonical `question_url` and `web_url`

## Optional: List Saved Direct Questions

Use this to browse recent saved direct questions across the organization.

```bash
curl -X GET "$BASE_URL/direct-questions?limit=25&offset=0" \
  -H "Authorization: Bearer $API_KEY"
```

If you already know the saved identifier, you can filter directly:

```bash
curl -X GET "$BASE_URL/direct-questions?question_id=<question_uuid>" \
  -H "Authorization: Bearer $API_KEY"
```

## Optional: Discover Available Filters (Country Scoped)

```bash
curl -X GET "$BASE_URL/filters?country=US" \
  -H "Authorization: Bearer $API_KEY"
```

Use this endpoint to fetch valid categorical values and counts before composing recruit/search/find filter payloads.

## Optional: Find a Single Agent

GET variant:

```bash
curl -X GET "$BASE_URL/agents/find?country=USA&state=PA&age_min=65&gender=male&match=all" \
  -H "Authorization: Bearer $API_KEY"
```

POST variant:

```bash
curl -X POST "$BASE_URL/agents/find" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "country": "USA",
    "state": "PA",
    "age_min": 65,
    "gender": "male",
    "match": "all"
  }'
```

## Optional: Retrieve Study Questions and Answers

```bash
curl -X GET "$BASE_URL/research-studies/<study_id>/questions" \
  -H "Authorization: Bearer $API_KEY"
```

Newly created questions may temporarily return empty `answers` arrays until jobs finish.

## Utility: List and Get Studies

List:

```bash
curl -X GET "$BASE_URL/research-studies?limit=25&offset=0" \
  -H "Authorization: Bearer $API_KEY"
```

Get one:

```bash
curl -X GET "$BASE_URL/research-studies/<study_id>" \
  -H "Authorization: Bearer $API_KEY"
```

## Pagination Patterns (Current State)

Pagination is currently inconsistent across endpoint families:

- `limit` + `offset` on study/group list endpoints
- `page` + `per_page` on agent search
- `page` + `page_size` on zeitgeist survey results

## Error Handling Patterns

### Invalid filters (`400`)

Typical response:

```json
{
  "error": {
    "code": "invalid_request",
    "message": "Invalid filters",
    "details": {
      "filters": {
        "ethnicity": ["unsupported value; use an exact documented value"]
      }
    }
  }
}
```

Recovery:

- Use exact full-string categorical values from `docs/api/guides/filters.md`.
- For complex filters, prefer JSON-body format (`POST /v1/agents/find`) over query encoding.

### Invalid categorical value (`422`, optional strict mode)

If `API_V1_STRICT_INVALID_FILTERS=1` is enabled, unsupported finite categorical values can
return `422` instead of silent zero-results:

```json
{
  "error": {
    "code": "invalid_request",
    "message": "Invalid filter value(s)",
    "details": {
      "filters": [
        {
          "field": "ethnicity",
          "value": "hispanic",
          "message": "unsupported value for ethnicity",
          "suggestions": ["Hispanic or Latino"]
        }
      ]
    }
  }
}
```

### Missing scope (`403`)

Typical response:

```json
{
  "error": {
    "code": "forbidden",
    "message": "Forbidden"
  }
}
```

Recovery:

- Ensure key scopes match the endpoint (`studies:read` or `studies:write`).
- For `GET /v1/jobs/{job_id}`, also ensure the job belongs to the same organization.

### Invalid completion steps (`400`)

Typical response:

```json
{
  "error": {
    "code": "invalid_request",
    "message": "Unknown step(s)",
    "details": {
      "steps": ["study_magic"]
    }
  }
}
```

Recovery:

- Omit `steps` to run the default completion sequence.
- If you supply `steps`, send a list of supported workflow step names only.

Always use the parameter pair documented on each endpoint.

## Billing Endpoints

Use billing-scoped keys:

- `billing:read` for subscription/usage/invoice reads
- `billing:write` for checkout/portal/cancel actions

Create checkout session:

```bash
curl -X POST "$BASE_URL/billing/checkout" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "success_url": "https://cat.fish.dog/organization?billing=success",
    "cancel_url": "https://cat.fish.dog/organization?billing=cancel"
  }'
```

Get billing snapshot:

```bash
curl -X GET "$BASE_URL/billing/subscription" \
  -H "Authorization: Bearer $API_KEY"
```

```bash
curl -X GET "$BASE_URL/billing/usage" \
  -H "Authorization: Bearer $API_KEY"
```

```bash
curl -X GET "$BASE_URL/billing/invoices?limit=20" \
  -H "Authorization: Bearer $API_KEY"
```
