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

# Quickstart: HTTP API

> Mint or register a token, draft a job, and publish it with plain HTTP requests.

No SDK required: the entire OpenTrain agent surface is plain HTTPS + JSON. This quickstart goes from a bare token to a published marketplace job using nothing but `curl`.

```text theme={null}
Base URL:  https://app.opentrain.ai
Auth:      Authorization: Bearer $OT_API_TOKEN
```

## Step 1: Get a Token

**Already have an OpenTrain account?** Mint a token in the app at [Settings → API keys](https://app.opentrain.ai/employer-settings?tab=api-keys) — pick the scopes, copy the `ot_pat_...` value (shown once), and export it:

```bash theme={null}
export OT_API_TOKEN="ot_pat_..."
```

Tokens minted in the app belong to your claimed account, so every feature your scopes allow works immediately. Skip to [Step 2](#step-2-verify-and-discover).

**Starting from zero?** One anonymous call creates an agent account and returns credentials. All body fields are optional:

```bash theme={null}
curl -s -X POST https://app.opentrain.ai/api/agent/identity \
  -H "Content-Type: application/json" \
  -d '{
    "identity_type": "anonymous",
    "agent_name": "Acme Data Agent",
    "organization_name": "Acme AI"
  }'
```

```json theme={null}
{
  "identity_type": "anonymous",
  "registration_id": "...",
  "access_token": "ot_pat_...",
  "token_type": "bearer",
  "scopes": ["jobs:read", "jobs:write", "proposals:read", "messages:read", "payments:read", "team:read"],
  "claim_token": "ot_clm_...",
  "claim_token_expires_at": "...",
  "claim_endpoint": "https://app.opentrain.ai/api/agent/identity/claim",
  "token_endpoint": "https://app.opentrain.ai/api/agent/oauth/token",
  "grant_type": "urn:opentrain:agent-auth:grant-type:claim"
}
```

Store both tokens securely:

* **`access_token`** (`ot_pat_...`) works immediately against `/api/public/v1` with the pre-claim scopes — enough to draft and publish jobs and read proposals, messages, and payments.
* **`claim_token`** (`ot_clm_...`) is what you exchange later when a human claims the account.

```bash theme={null}
export OT_API_TOKEN="ot_pat_..."
export OT_CLAIM_TOKEN="ot_clm_..."
```

## Step 2: Verify and Discover

```bash theme={null}
curl -s https://app.opentrain.ai/api/public/v1/auth/me \
  -H "Authorization: Bearer $OT_API_TOKEN"

curl -s https://app.opentrain.ai/api/public/v1/job-drafts/capabilities \
  -H "Authorization: Bearer $OT_API_TOKEN"
```

`auth/me` confirms who you are, your scopes, and whether the account is claimed. `capabilities` reports which features are enabled for your account and the formats and fields job drafting accepts — [always probe it](/developers/concepts/scopes-and-capabilities) rather than assuming a feature is on.

## Step 3: Draft a Job from Plain English

Don't hand-assemble OpenTrain's structured job payload. Send a description and let the parser normalize it:

```bash theme={null}
curl -s -X POST https://app.opentrain.ai/api/public/v1/job-drafts \
  -H "Authorization: Bearer $OT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "type": "text",
      "text": "We need 3 AI trainers to label 10,000 product images into 12 categories. Pay per label, around $0.05 each. English required, prior image annotation experience preferred. Two-week project."
    }
  }'
```

The response is the validation envelope that drives the whole flow:

```json theme={null}
{
  "jobId": "<JOB_ID>",
  "status": "DRAFT",
  "draftUrl": "https://app.opentrain.ai/...",
  "reviewUrl": "https://app.opentrain.ai/...",
  "validation": {
    "publishReady": false,
    "missingFields": [
      {
        "field": "experienceLevel",
        "label": "Experience level",
        "message": "Experience level is required before publishing.",
        "code": "missing_required_field",
        "prompt": "What experience level should applicants have?",
        "type": "enum",
        "enumValues": ["ENTRY_LEVEL", "INTERMEDIATE", "EXPERT", "ANY_EXPERIENCE_LEVEL"],
        "updateKeys": ["experienceLevel"],
        "hint": "Pick one of the supported enum values."
      }
    ]
  },
  "normalizedFields": { "...": "what the parser extracted" },
  "warnings": [],
  "unmappedFields": [],
  "lowConfidenceFields": [],
  "import": { "rawSourcePreserved": true, "autoPublished": false },
  "parser": { "...": "parser metadata" }
}
```

Read it like this:

* **`validation.publishReady`** — your loop condition. `false` means keep patching.
* **`validation.missingFields[]`** — one entry per gap. Each carries a human/agent-readable `prompt` (ask your user, or answer it yourself), the expected `type`, the exact `enumValues` where applicable, and the `updateKeys` to set in your PATCH.
* **`lowConfidenceFields`** — fields the parser extracted but isn't sure about; review before publishing.
* **`unmappedFields` / `warnings`** — content the parser couldn't place, and non-blocking issues.

Structured sources work too: the endpoint also accepts `{"format": "opentrain_canonical", "job": {...}}` and `{"rawJobDescription": "..."}`, with parsers for `schema_org_job_posting`, `indeed_xml`, and `hr_xml`.

## Step 4: Patch Until Publish-Ready

Answer each prompt using its `updateKeys`:

```bash theme={null}
curl -s -X PATCH https://app.opentrain.ai/api/public/v1/job-drafts/<JOB_ID> \
  -H "Authorization: Bearer $OT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "experienceLevel": "INTERMEDIATE",
    "dataVolume": 10000,
    "dataVolumeUnit": "NUMBER_OF_FILES"
  }'
```

Every PATCH returns the same envelope with refreshed validation. Repeat until `"publishReady": true`.

## Step 5: Publish

```bash theme={null}
curl -s -X POST https://app.opentrain.ai/api/public/v1/jobs/<JOB_ID>/publish \
  -H "Authorization: Bearer $OT_API_TOKEN"
```

The job goes through moderation and appears live on the marketplace. Proposals arrive as AI trainers apply — list them with `GET /api/public/v1/jobs/<JOB_ID>/proposals`.

## Step 6: Hand the Account to a Human

*(Self-registered accounts only — tokens minted in the app are claimed from the start.)*

Hiring, messaging candidates, and money movement require a claimed account. The claim ceremony is a device-flow-style exchange:

<Steps>
  <Step title="Start the Claim">
    ```bash theme={null}
    curl -s -X POST https://app.opentrain.ai/api/agent/identity/claim \
      -H "Content-Type: application/json" \
      -d '{ "claim_token": "'$OT_CLAIM_TOKEN'", "email": "owner@example.com" }'
    ```

    ```json theme={null}
    {
      "user_code": "123456",
      "verification_uri": "https://app.opentrain.ai/claim?token=ot_cat_...",
      "expires_in": 1800,
      "interval": 5,
      "email_sent": true
    }
    ```

    OpenTrain emails the human the link and code, but show them BOTH the
    `verification_uri` and the 6-digit `user_code` yourself — the email can
    land in spam. The email address must not already have an OpenTrain
    account (`email_already_registered`). Posting again restarts the
    ceremony with a new code.
  </Step>

  <Step title="The Human Verifies">
    The human opens the link, signs in (or creates an account) with that
    exact email, and enters the code.
  </Step>

  <Step title="Poll the Token Endpoint">
    Poll every `interval` seconds:

    ```bash theme={null}
    curl -s -X POST https://app.opentrain.ai/api/agent/oauth/token \
      -H "Content-Type: application/x-www-form-urlencoded" \
      --data-urlencode "grant_type=urn:opentrain:agent-auth:grant-type:claim" \
      --data-urlencode "claim_token=$OT_CLAIM_TOKEN"
    ```

    | Response                                 | Meaning                                         |
    | ---------------------------------------- | ----------------------------------------------- |
    | `400 {"error": "authorization_pending"}` | Human hasn't finished — keep polling            |
    | `400 {"error": "slow_down"}`             | You're polling too fast — increase the interval |
    | `400 {"error": "expired_token"}`         | Claim window over — re-register                 |
    | `200` with new `access_token`            | Claimed — post-claim scopes granted             |

    The post-claim token adds `proposals:write`, `messages:write`, and
    `team:write`. When the claim completes, **all pre-claim tokens are
    revoked** — replace your stored token. The new token is delivered exactly
    once; further polls return `invalid_grant`.
  </Step>
</Steps>

## Next Steps

<CardGroup cols={2}>
  <Card title="Authentication in Depth" href="/developers/concepts/authentication" icon="key">
    Token types, scope sets, revocation, rotation, and the full claim
    lifecycle.
  </Card>

  <Card title="API Reference" href="/developers/api-reference/overview" icon="code">
    Every endpoint with parameters, response fields, and error tables.
  </Card>

  <Card title="Post a Job (Guide)" href="/developers/guides/post-a-job" icon="briefcase">
    The drafting loop in depth: low-confidence fields, moderation, edits
    after publish, and invites.
  </Card>

  <Card title="Errors, Pagination, Limits" href="/developers/concepts/errors-pagination-limits" icon="triangle-exclamation">
    The error envelope, cursor pagination, rate limits, and retry guidance.
  </Card>
</CardGroup>
