> ## Documentation Index
> Fetch the complete documentation index at: https://docs.phosra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# First-Time Setup

> Complete walkthrough for integrating Phosra into your application

This guide covers integrating Phosra into your application from scratch, including API key provisioning and your first enforcement run.

<Note>
  **One auth header, everywhere.** Every raw HTTP call on this page sends your key as
  `Authorization: Bearer $PHOSRA_API_KEY` — the one canonical form (`X-Api-Key` is accepted as
  an exact equivalent, but we use `Bearer` consistently so keys, WorkOS JWTs, and MCP tokens all
  share one header). Full detail: [Authentication](/authentication).
</Note>

## 1. Create Your Account

Sign up at [dashboard.phosra.com/signup](https://dashboard.phosra.com/signup) via the WorkOS AuthKit hosted sign-up flow. There is no `/auth/register` endpoint on the Go API — account creation is handled by WorkOS. Once signed in, continue to step 2. (Full walkthrough: [Create your account & get keys](/platform/create-account).)

## 2. Get an API Key

Use the [developer console's Keys page](https://dashboard.phosra.com/dashboard/developers/keys) or call the API with your WorkOS session:

```bash theme={null}
curl -X POST https://prodapi.phosra.com/api/v1/developers/orgs \
  -H "Authorization: Bearer <workos_access_token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "My Integration"}'

# Then create a key for the org:
curl -X POST https://prodapi.phosra.com/api/v1/developers/orgs/{orgId}/keys \
  -H "Authorization: Bearer <workos_access_token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "server-key", "scopes": ["read:families","write:policies","write:enforcement"]}'
```

The response includes the key once. Store it immediately:

```bash theme={null}
# Production key
export PHOSRA_API_KEY="phosra_live_4a1b2c..."

# Sandbox key (use against https://phosra-api-sandbox-production.up.railway.app/api/v1)
export PHOSRA_API_KEY="phosra_test_4a1b2c..."
```

**Key format:** `phosra_live_<64 hex chars>` (production) or `phosra_test_<64 hex chars>` (sandbox). There is no `_sk_` infix.

## 3. Use Quick Setup for Onboarding

The fastest path for your end users. When a parent signs up and adds their first child in your app, call the quick setup endpoint:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://prodapi.phosra.com/api/v1/setup/quick \
    -H "Authorization: Bearer $PHOSRA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "child_name": "Emma",
      "birth_date": "2016-03-15",
      "strictness": "recommended"
    }'
  ```

  ```typescript TypeScript theme={null}
  import { PhosraClient } from "@phosra/sdk";

  const phosra = new PhosraClient({ apiKey: process.env.PHOSRA_API_KEY });

  const setup = await phosra.setup.quick({
    child_name: "Emma",
    birth_date: "2016-03-15",
    strictness: "recommended",
  });
  // setup.family.id, setup.child.id — store both
  ```

  ```python Python theme={null}
  import os, requests

  r = requests.post(
      "https://prodapi.phosra.com/api/v1/setup/quick",
      headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
      json={"child_name": "Emma", "birth_date": "2016-03-15", "strictness": "recommended"},
      timeout=30,
  )
  r.raise_for_status()
  setup = r.json()
  family_id, child_id = setup["family"]["id"], setup["child"]["id"]
  ```

  ```go Go theme={null}
  package main

  import (
  	"bytes"
  	"encoding/json"
  	"fmt"
  	"net/http"
  	"os"
  )

  func main() {
  	payload, _ := json.Marshal(map[string]string{
  		"child_name": "Emma", "birth_date": "2016-03-15", "strictness": "recommended",
  	})
  	req, _ := http.NewRequest("POST",
  		"https://prodapi.phosra.com/api/v1/setup/quick", bytes.NewReader(payload))
  	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
  	req.Header.Set("Content-Type", "application/json")

  	resp, err := http.DefaultClient.Do(req)
  	if err != nil {
  		panic(err)
  	}
  	defer resp.Body.Close()

  	var setup struct {
  		Family struct{ ID, Name string } `json:"family"`
  		Child  struct{ ID, Name string } `json:"child"`
  	}
  	json.NewDecoder(resp.Body).Decode(&setup)
  	fmt.Println(setup.Family.ID, setup.Child.ID) // store both for step 6
  }
  ```
</CodeGroup>

This returns a family, child, and an **active policy** with age-appropriate rules populated across the applicable [rule categories](/ocss/rule-reference) for the requested strictness level, plus `age_group`, `max_ratings`, and `rule_summary`. Store the `family.id` and `child.id` for subsequent calls.

<Tip>
  Prefer to try this without a key first? The [Quickstart](/quickstart) runs the exact same call against the open sandbox — no auth — and shows the real response.
</Tip>

## 4. Connect Platforms

For each platform the family uses, create a compliance link:

```bash theme={null}
curl -X POST https://prodapi.phosra.com/api/v1/compliance \
  -H "Authorization: Bearer $PHOSRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "family_id": "FAMILY_UUID",
    "platform_id": "nextdns",
    "credentials": "nextdns-api-key"
  }'
```

List available platforms with `GET /platforms` to see what integrations are supported, and check each platform's `enforcement_mode` — one of `dns`, `device`, or `manual_attested`. `dns` and `device` apply rules programmatically (a live DNS-provider write, or on-device via the Phosra app); `manual_attested` returns guided steps for the parent to attest. See [Platforms & enforcement modes](/concepts/platforms).

## 5. Register Webhooks

Set up webhooks to receive real-time event notifications (policy changes, new family members, etc.):

```bash theme={null}
curl -X POST https://prodapi.phosra.com/api/v1/webhooks \
  -H "Authorization: Bearer $PHOSRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "family_id": "FAMILY_UUID",
    "url": "https://yourapp.com/webhooks/phosra",
    "events": ["policy.updated", "family.created"]
  }'
```

**Note:** webhooks do **not** fire on enforcement job completion. To verify enforcement results, poll `GET /enforcement/jobs/{jobId}/results` — see [Enforcement](/concepts/enforcement).

## 6. Trigger Enforcement

Push the policy to all connected platforms, then poll the returned job for results:

<CodeGroup>
  ```bash cURL theme={null}
  BASE="https://prodapi.phosra.com/api/v1"
  CHILD_ID="CHILD_UUID"   # from setup.child.id in step 3

  # Trigger — returns a job (202 Accepted)
  JOB_ID=$(curl -s -X POST "$BASE/children/$CHILD_ID/enforce" \
    -H "Authorization: Bearer $PHOSRA_API_KEY" | jq -r .id)

  # Poll results
  curl -s "$BASE/enforcement/jobs/$JOB_ID/results" \
    -H "Authorization: Bearer $PHOSRA_API_KEY"
  ```

  ```typescript TypeScript theme={null}
  import { PhosraClient } from "@phosra/sdk";

  const phosra = new PhosraClient({ apiKey: process.env.PHOSRA_API_KEY });
  const childId = "CHILD_UUID"; // from setup.child.id in step 3

  const job = await phosra.enforcement.trigger(childId);
  // poll until done
  const results = await phosra.enforcement.getResults(job.id);
  console.log(results);
  ```

  ```python Python theme={null}
  import os, requests

  BASE = "https://prodapi.phosra.com/api/v1"
  child_id = "CHILD_UUID"  # from setup["child"]["id"] in step 3

  session = requests.Session()
  session.headers["Authorization"] = f"Bearer {os.environ['PHOSRA_API_KEY']}"

  job = session.post(f"{BASE}/children/{child_id}/enforce", timeout=30).json()
  results = session.get(f"{BASE}/enforcement/jobs/{job['id']}/results", timeout=30).json()
  print(results)
  ```

  ```go Go theme={null}
  package main

  import (
  	"encoding/json"
  	"fmt"
  	"io"
  	"net/http"
  	"os"
  )

  func main() {
  	base := "https://prodapi.phosra.com/api/v1"
  	childID := "CHILD_UUID" // from setup.Child.ID in step 3
  	key := os.Getenv("PHOSRA_API_KEY")

  	// Trigger enforcement — returns a job.
  	req, _ := http.NewRequest("POST", base+"/children/"+childID+"/enforce", nil)
  	req.Header.Set("Authorization", "Bearer "+key)
  	resp, err := http.DefaultClient.Do(req)
  	if err != nil {
  		panic(err)
  	}
  	var job struct {
  		ID string `json:"id"`
  	}
  	json.NewDecoder(resp.Body).Decode(&job)
  	resp.Body.Close()

  	// Poll results.
  	rreq, _ := http.NewRequest("GET",
  		base+"/enforcement/jobs/"+job.ID+"/results", nil)
  	rreq.Header.Set("Authorization", "Bearer "+key)
  	rresp, err := http.DefaultClient.Do(rreq)
  	if err != nil {
  		panic(err)
  	}
  	defer rresp.Body.Close()
  	body, _ := io.ReadAll(rresp.Body)
  	fmt.Printf("%s\n", body)
  }
  ```
</CodeGroup>

A non-empty `manual_steps` array on any result means that platform is parent-guided, not programmatically applied.

## Handle errors

Every failure returns a JSON envelope of the shape `{"error", "message", "code"}` (OCSS routes add
`class` and, on standing checks, `failed_step`). Branch on `code` + `class`, never on the human-readable
`message`. The full status/class matrix — every 4xx and 5xx, its cause, and its fix — is in the
**[Errors reference](/errors)**. The one you will hit first while wiring auth:

| Code  | Meaning                     | Fix                                                                                                                                |
| ----- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `401` | `invalid API key`           | Key not in this environment's store — a `phosra_test_` key only works against the sandbox, `phosra_live_` only against production. |
| `403` | `no API scopes in context`  | Request reached a `/developer/*` route with no `phosra_` key at all — attach `Authorization: Bearer $PHOSRA_API_KEY`.              |
| `403` | `missing required scope: …` | Key authenticated but lacks the scope — mint a key with the scope named in the message.                                            |
| `429` | rate limited                | Wait until `X-RateLimit-Reset`, then retry.                                                                                        |
| `500` | internal error              | Transient — retry with exponential backoff.                                                                                        |

## Base URLs

| Environment | Base URL                                                                                                 |
| ----------- | -------------------------------------------------------------------------------------------------------- |
| Production  | `https://prodapi.phosra.com/api/v1`                                                                      |
| Sandbox     | `https://phosra-api-sandbox-production.up.railway.app/api/v1` — seeded demo family + `phosra_test_` keys |
| Local dev   | `http://localhost:8080/api/v1`                                                                           |

<Note>
  `api.phosra.com` has no DNS record — do not use it. Use `prodapi.phosra.com` for production. `phosra-api.fly.dev` is a legacy Fly.io address being decommissioned; use `prodapi.phosra.com` instead.
</Note>

## Checklist

* [ ] Account created via WorkOS AuthKit
* [ ] Developer org created and API key provisioned (test and production)
* [ ] Quick setup tested with a sandbox child
* [ ] At least one platform connected; `enforcement_mode` checked
* [ ] Enforcement triggered and results polled (not webhook-waited)
* [ ] Error handling implemented for 4xx and 5xx responses
