# How to build a profile

A profile you create yourself starts as a shell: a name, an ORCID proof, and
nothing else. It has no publications, no expertise narrative, and no embedding
index, so semantic matching cannot see it. There are two ways to fill it in:
build it yourself with your own AI, or ask an operator to build it for you.
Prosopia does not build anything itself either way. It records the job, hands
it to a remote worker, and the worker reports progress back.

## Self-service: build your own profile

This is the normal way to fill in your profile. Give your AI the
[build-profile](https://prosopia.databio.org/skills/build-profile/SKILL.md)
skill, which walks it through the Prosopia API: fetching your publications,
writing your expertise narrative, and pushing everything to your profile.
You run it yourself, on your own AI, so the compute cost is yours.

The skill is a plain Markdown file your AI reads — no special tools or
vendor lock-in required. Download it or point your AI at the URL.

## Admin-triggered: an operator builds it for you

Operators can trigger a build for any profile from the admin panel, or
directly with these two endpoints. Both are operator-gated:

```
POST /api/manage/profiles/admin/build/{ref}
GET  /api/manage/profiles/admin/build/{ref}
```

Operators use this for profiles they need and are willing to pay for — for
example, building a profile for a journal reviewer who has not built their
own yet. If you are a profile owner, prefer the self-service skill above; ask
an operator only if you cannot build your own.

### Prerequisites

- You are an operator: the operator token, or a session whose ORCID is on the
  deployment's admin list.
- The deployment has a build runner configured — the session probe's
  `build_available` field must be `true`, or build requests return `503`.

> Depending on how the deployment is configured, builds may be rate-limited or
> require credits. A rejection for this reason also comes back as an error
> from the build endpoint.

### Steps

1. Enqueue the build. The body is optional; `level` is `lite` or `full`
   (default `full`):

    ```bash
    curl -H "Authorization: Bearer <operator-token>" \
      -H "Content-Type: application/json" \
      -X POST https://prosopia.databio.org/api/manage/profiles/admin/build/jane-doe \
      -d '{"level": "full"}'
    ```

    A successful enqueue returns `202 Accepted` with the new job, created at
    status `queued`:

    ```json
    {
      "job_id": 12,
      "status": "queued",
      "level": "full",
      "requested_at": "2026-08-26T18:00:00+00:00",
      "phases_completed": [],
      "runner": "modal"
    }
    ```

2. Poll for progress. Read the latest job for the profile until it reaches a
   terminal status. The worker reports `running`, then `done` or `failed`, and
   stamps `phases_completed` as it goes:

    ```bash
    curl -H "Authorization: Bearer <operator-token>" \
      https://prosopia.databio.org/api/manage/profiles/admin/build/jane-doe
    ```

    A profile that has never been built reads as `{"status": "none"}` with the
    same shape as a real job.

3. When the job reaches `done`, the built profile is already back on the server
   and its caches are refreshed. It now has content and an embedding index, so it
   can appear in semantic matching. Publishing it is a separate step, and stays
   the owner's decision — an admin build does not publish the profile. See
   [How to share your profile](/prosopia/how-to/share-your-profile.md).

## The owner-facing build endpoint (deprecated)

`POST /api/manage/profiles/{ref}/build` and
`GET /api/manage/profiles/{ref}/build` still exist and work the same way as
before — the owner enqueues and polls a build for their own profile — but
they are deprecated in favor of the self-service approach above, which does
not put the build cost on the operator. New integrations should not build
against these two endpoints.

## The callback endpoint

`POST /api/manage/profiles/{ref}/build/callback`: the runner's progress
reports, for either trigger path above. This is **not** a browser or CLI
endpoint. The worker authenticates it with a shared bearer secret, not a
session or operator token. You never call it.

## Why the level `deep` is not offered to admin builds

The admin build endpoint accepts `lite` or `full`. `deep` is deliberately
absent. It needs supplied private sources (a CV, personal records, website
URLs) that only the profile's owner has, so an operator-triggered build would
only ever produce a failed job. The self-service approach, run by the owner's
own AI, can be given those sources directly.

## What can go wrong

- **`401`**: you did not present an operator credential.
- **`403`**: your credential is not an operator's (a plain owner or editor
  session cannot call the admin endpoints).
- **`400`**: an unknown `level`. Only `lite` and `full` are accepted.
- **`404`**: no profile resolves to that slug.
- **`409`**: a build of this profile is already `queued` or `running`. One build
  runs at a time; wait for it to finish before starting another. (A wedged job is
  released after several hours, so you are never blocked forever.)
- **`503`**: no build runner is configured on this deployment, or the runner
  could not be reached. If a spawn fails, the job is marked `failed` so you can
  retry immediately instead of waiting out a `409`.
