DEVELOPER DOCUMENTATION
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.
Base URL: https://app.opentrain.aiAuth: Authorization: Bearer $OT_API_TOKENStep 1: Get a Token
Section titled “Step 1: Get a Token”Already have an OpenTrain account? Mint a token in the app at Settings → API keys — choose Full access for a trusted ongoing agent or Fine-grained for a narrow integration, copy the ot_pat_... value (shown once), and export it:
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.
Starting from zero? One anonymous call creates an agent account and returns credentials. All body fields are optional:
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" }'{ "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/v1with 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.
export OT_API_TOKEN="ot_pat_..."export OT_CLAIM_TOKEN="ot_clm_..."Step 2: Verify and Discover
Section titled “Step 2: Verify and Discover”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 rather than assuming a feature is on.
Step 3: Draft a Job from Plain English
Section titled “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:
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:
{ "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.falsemeans keep patching.validation.missingFields[]— one entry per gap. Each carries a human/agent-readableprompt(ask your user, or answer it yourself), the expectedtype, the exactenumValueswhere applicable, and theupdateKeysto 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
Section titled “Step 4: Patch Until Publish-Ready”Answer each prompt using its updateKeys:
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
Section titled “Step 5: Publish”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
Section titled “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:
Start the Claim
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" }'{ "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.
The Human Verifies
The human opens the link, signs in (or creates an account) with that exact email, and enters the code.
Poll the Token Endpoint
Poll every interval seconds:
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.
Next Steps
Section titled “Next Steps”Token types, scope sets, revocation, rotation, and the full claim lifecycle.
Every endpoint with parameters, response fields, and error tables.
The drafting loop in depth: low-confidence fields, moderation, edits after publish, and invites.
The error envelope, cursor pagination, rate limits, and retry guidance.