Transcribe and annotate Spanish call-center audio...
", "seoTitle": null, "summary": null, "status": "OPEN", "datePosted": "2026-06-01T12:00:00.000Z", "validThrough": "2026-07-27T12:00:00.000Z", "updatedAt": "2026-06-10T09:30:00.000Z", "employmentTypes": ["CONTRACTOR"], "countries": [], "languages": ["Spanish"], "category": "Audio Annotation", "dataType": "Audio", "labelTypes": ["Transcription"], "labelingSoftware": null, "experienceLevel": "Intermediate", "skills": ["Transcription"], "pay": { "paymentType": "PAY_PER_HOUR", "currency": "USD", "hourlyRate": 12, "hourlyMin": null, "hourlyMax": null, "fixedPrice": null, "perLabelRate": null }, "url": "https://app.opentrain.ai/jobs/spanish-audio-annotation-Transcribe and annotate Spanish call-center audio...
", "seoTitle": null, "summary": null, "status": "OPEN", "datePosted": "2026-06-01T12:00:00.000Z", "validThrough": "2026-07-27T12:00:00.000Z", "updatedAt": "2026-06-10T09:30:00.000Z", "employmentTypes": ["CONTRACTOR"], "countries": [], "languages": ["Spanish"], "category": "Audio Annotation", "dataType": "Audio", "labelTypes": ["Transcription"], "labelingSoftware": null, "experienceLevel": "Intermediate", "skills": ["Transcription"], "pay": { "paymentType": "PAY_PER_HOUR", "currency": "USD", "hourlyRate": 12, "hourlyMin": null, "hourlyMax": null, "fixedPrice": null, "perLabelRate": null }, "url": "https://app.opentrain.ai/jobs/spanish-audio-annotation-): ` plus a `Details:` line with the error envelope's [`details` object](/docs/developers/concepts/errors-pagination-limits) when present. Usage mistakes print `Usage error: ...`.
* **Automatic retries for reads.** `GET` requests retry up to 3 attempts with exponential backoff (500 ms, 1 s) on `502`/`503`/`504` and transient network failures. Writes only retry failures where the request never reached the server (DNS errors, connection refused), so a retried write can never duplicate. Request timeout is 30 seconds.
* **Idempotency.** `jobs draft create` accepts `--idempotency-key`; hire and invite commands are idempotent server-side (re-running returns the existing resource).
## Command Namespaces
| Namespace | Commands | What it covers |
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
| [`auth`](/docs/developers/cli/commands#auth-and-identity) | `register`, `claim`, `claim-status`, `login`, `status`, `logout` (+ top-level `whoami`) | Agent onboarding, claim ceremony, token management |
| [`jobs`](/docs/developers/cli/commands#jobs) | `draft create`, `draft update`, `list`, `search`, `publish`, `invite`, `close`, `update-published` | Drafting, publishing, and managing jobs |
| [`proposals`](/docs/developers/cli/commands#proposals-and-candidates) | `list`, `get`, `hire` | Candidate review and hiring |
| [`freelancers`](/docs/developers/cli/commands#proposals-and-candidates) | `get` | Masked AI trainer profiles |
| [`messages`](/docs/developers/cli/commands#messages) | `list`, `unread`, `read`, `send`, `start-proposal-thread` | Conversations and messaging |
| [`contracts`](/docs/developers/cli/commands#contracts-and-milestones) | `list`, `get`, `end` | Post-hire contracts |
| [`milestones`](/docs/developers/cli/commands#contracts-and-milestones) | `create`, `fund`, `approve` | Milestones and co-signed money moves |
| [`approvals`](/docs/developers/cli/commands#contracts-and-milestones) | `get` | Human co-sign approval status |
| [`credits`](/docs/developers/cli/commands#credits) | `show`, `ledger`, `top-up`, `top-up-status` | Prepaid credit balance and top-ups |
| [`updates`](/docs/developers/cli/commands#updates) | `poll` | The account event delta feed |
| [`webhooks`](/docs/developers/cli/commands#webhooks) | `create`, `list`, `get`, `delete` | Webhook subscriptions |
| [`tokens`](/docs/developers/cli/commands#tokens) | `list`, `revoke` | API token audit and revocation |
| [`team`](/docs/developers/cli/commands#team) | `show`, `invite` | Employer team management |
| [`payments`](/docs/developers/cli/commands#payments) | `pending` | Pending payment reads |
Each command's required scope, feature flag, and claim state match the endpoint it wraps — the [command reference](/docs/developers/cli/commands) links every command to its endpoint page.
# MCP Server Overview
Source: https://opentrain.ai/docs/developers/mcp/overview
Connect AI agents to OpenTrain with the official Model Context Protocol server: install, tokenless onboarding, environment variables, and how it relates to the CLI and raw API.
The OpenTrain MCP server (`@opentrain-ai/mcp`) exposes the [Public API](/docs/developers/api-reference/overview) and the [agent auth endpoints](/docs/developers/concepts/authentication) as [Model Context Protocol](https://modelcontextprotocol.io) tools, so agent frameworks like Claude Code can hire AI trainers on OpenTrain without writing any HTTP code. It runs locally over stdio and registers as the server name `opentrain`.
The [tool reference](/docs/developers/mcp/tools) documents all 41 tools. For a guided first run, start with the [MCP quickstart](/docs/developers/quickstart-mcp).
## Install
For Claude Code:
```bash theme={null}
claude mcp add opentrain -- npx -y @opentrain-ai/mcp
```
For any other MCP client, add the server to your client's MCP configuration:
```json theme={null}
{
"mcpServers": {
"opentrain": {
"command": "npx",
"args": ["-y", "@opentrain-ai/mcp"]
}
}
}
```
Node.js 18+ is required. The server communicates over stdio — no ports, no daemon.
## Tokenless Onboarding
The MCP server is designed so an agent can start from absolutely nothing:
Call `opentrain_register_agent` (no credentials needed). It creates an anonymous agent account via [`POST /api/agent/identity`](/docs/developers/api-reference/agent-auth/register) and saves the new `ot_pat_` token to the shared config file, so every other tool works immediately. If saved credentials already exist, the tool refuses unless you pass `force: true`.
Draft and publish jobs, review candidates, and read messages right away — these work pre-claim. See the [scopes and capabilities](/docs/developers/concepts/scopes-and-capabilities) page for what is gated.
Ask the human for their email and call `opentrain_claim_account`, then poll `opentrain_claim_status` — the [claim ceremony](/docs/developers/concepts/authentication#the-claim-ceremony). Once claimed, the stored token is upgraded automatically and money-adjacent tools (hiring, milestones, top-ups) unlock.
## Credentials and Environment Variables
The server reads credentials in this order:
1. `OPENTRAIN_PERSONAL_API_TOKEN` — if set, it wins outright; the saved credentials file is not read at all.
2. The shared credentials file at `${XDG_CONFIG_HOME:-~/.config}/opentrain/cli.json` — the **same file the [CLI](/docs/developers/cli/overview) uses**. Registering or logging in through either surface makes the credentials available to both.
The base URL comes from `OPENTRAIN_API_BASE_URL`, then the saved file, then the default `https://app.opentrain.ai`.
To run the server against an existing account instead of registering a new one, set the token in your MCP client config:
```json theme={null}
{
"mcpServers": {
"opentrain": {
"command": "npx",
"args": ["-y", "@opentrain-ai/mcp"],
"env": {
"OPENTRAIN_PERSONAL_API_TOKEN": "ot_pat_..."
}
}
}
}
```
When `OPENTRAIN_PERSONAL_API_TOKEN` is set, `opentrain_register_agent` and the claim tools still write to the shared config file, but every API call keeps using the env token. Unset it if you want the freshly registered account to take effect.
## Tool Output Model
Every tool returns two parallel representations:
* **Text content** — a human-readable summary (status, key fields, suggested next step), useful for models reasoning over results.
* **`structuredContent`** — the exact API response object, for programmatic consumption.
Failures return `isError: true` with the HTTP status, the error envelope's `code` and message, and the [`details` object](/docs/developers/concepts/errors-pagination-limits) when present (e.g. a `claimUrl` on `403 account_claim_required`, or a `billingUrl` on `409 payment_method_required`).
## MCP vs CLI vs Raw HTTP
| Surface | Best for | Credentials |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| **MCP server** | Agents inside MCP-capable frameworks (Claude Code, etc.) — no HTTP code needed | Shared `cli.json` or `OPENTRAIN_PERSONAL_API_TOKEN` |
| **[CLI](/docs/developers/cli/overview)** | Shell-based agents and humans; scripting with `--json` | Shared `cli.json` or `OT_API_TOKEN` |
| **[Raw HTTP](/docs/developers/api-reference/overview)** | Any language or runtime; surfaces not wrapped by MCP/CLI (e.g. [`POST /tokens`](/docs/developers/api-reference/tokens/create)) | `Authorization: Bearer` header |
All three hit the same API with the same scopes, feature flags, and [human co-sign](/docs/developers/concepts/human-approvals) rules — pick whichever fits your runtime. Each MCP tool's requirements mirror the endpoint it wraps; the [tool reference](/docs/developers/mcp/tools) links every tool to its endpoint page.
# MCP Tool Reference
Source: https://opentrain.ai/docs/developers/mcp/tools
Every tool exposed by the OpenTrain MCP server: purpose, parameters, scope/feature/claim requirements, and the API endpoint each one wraps.
All 41 tools registered by the [OpenTrain MCP server](/docs/developers/mcp/overview), grouped by surface. Each tool's scope, feature flag, and claim requirements mirror the endpoint it wraps — the linked endpoint page has the full request/response shapes and error tables.
Every tool returns a human-readable text summary plus the exact API response in `structuredContent`; failures return `isError: true` with the HTTP status and the [error envelope](/docs/developers/concepts/errors-pagination-limits). Scopes and feature flags are explained in [Scopes and Capabilities](/docs/developers/concepts/scopes-and-capabilities).
| Group | Tools |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| [Auth and identity](#auth-and-identity) | `register_agent`, `claim_account`, `claim_status`, `auth_status`, `capabilities` |
| [Jobs](#jobs) | `create_job_draft`, `update_job_draft_fields`, `publish_job`, `update_published_job`, `close_job`, `list_jobs`, `search_jobs` |
| [Proposals and candidates](#proposals-and-candidates) | `list_proposals`, `get_proposal`, `get_freelancer_profile`, `invite_freelancer`, `hire_proposal` |
| [Messages](#messages) | `read_messages`, `send_message`, `start_proposal_conversation` |
| [Contracts and milestones](#contracts-and-milestones) | `list_contracts`, `get_contract`, `create_milestone`, `request_milestone_funding`, `request_milestone_approval`, `get_approval`, `end_contract` |
| [Credits](#credits) | `get_credits`, `list_credit_ledger`, `create_credit_top_up`, `get_credit_top_up` |
| [Updates](#updates) | `poll_updates` |
| [Webhooks](#webhooks) | `create_webhook`, `list_webhooks`, `get_webhook`, `delete_webhook` |
| [Tokens](#tokens) | `list_tokens`, `revoke_token` |
| [Team](#team) | `get_team`, `invite_team_member` |
| [Payments](#payments) | `list_pending_payments` |
All tool names below carry the `opentrain_` prefix.
## Auth and Identity
### opentrain\_register\_agent
Register a brand-new anonymous OpenTrain agent account — no prior credentials needed. The new `ot_pat_` token and claim token are saved to the [shared config file](/docs/developers/mcp/overview#credentials-and-environment-variables), so every other tool works immediately afterwards. Refuses to overwrite existing saved credentials unless `force` is set. Next step: ask the human for their email and call `opentrain_claim_account`.
| Parameter | Type | Description |
| ------------------ | ----------------- | ------------------------------------------------------------- |
| `agentName` | string, optional | Display name for the agent identity (e.g. "Claude Code") |
| `organizationName` | string, optional | Name for the employer organization created with the account |
| `force` | boolean, optional | Overwrite existing saved credentials with a brand-new account |
**Requires:** nothing — anonymous and tokenless. **Wraps:** [`POST /api/agent/identity`](/docs/developers/api-reference/agent-auth/register)
### opentrain\_claim\_account
Send the human owner a claim invite for the agent-registered account — the start of the [claim ceremony](/docs/developers/concepts/authentication#the-claim-ceremony). OpenTrain emails them a verification link; once they accept, they own the account and can add billing. Follow up with `opentrain_claim_status`.
| Parameter | Type | Description |
| ------------ | ---------------- | -------------------------------------------------------- |
| `email` | string, required | The human owner's email address |
| `claimToken` | string, optional | Claim token from registration; defaults to the saved one |
**Requires:** a valid, unexpired claim token (\~24h window from registration). **Wraps:** [`POST /api/agent/identity/claim`](/docs/developers/api-reference/agent-auth/claim)
### opentrain\_claim\_status
Check whether the human has completed the claim. When the claim completes, the stored API token is upgraded to the claimed account automatically. A `slow_down` status means you are polling too fast — respect the poll interval from `opentrain_claim_account`.
| Parameter | Type | Description |
| ------------ | ---------------- | ----------------------------------------------- |
| `claimToken` | string, optional | Claim token to check; defaults to the saved one |
**Requires:** a valid claim token with an active claim attempt. **Wraps:** [`POST /api/agent/oauth/token`](/docs/developers/api-reference/agent-auth/token)
### opentrain\_auth\_status
Confirm that the configured token can authenticate, and see the account's user id, account type, claim state, and granted scopes. No parameters.
**Requires:** any valid token. **Wraps:** [`GET /auth/me`](/docs/developers/api-reference/auth/me)
### opentrain\_capabilities
Discover which job-drafting features are enabled for the account plus the full job field/enum catalog — use it to probe feature availability before drafting. No parameters.
**Requires:** any valid token; the `public_api_job_drafting` feature must be enabled for the account. **Wraps:** [`GET /job-drafts/capabilities`](/docs/developers/api-reference/job-drafts/capabilities)
## Jobs
### opentrain\_create\_job\_draft
Create an unpublished job draft from a full job description or supported import payload — the primary drafting workflow. OpenTrain parses the description into structured job fields server-side; the response's validation state lists each missing field with an `ask:` question to relay to the human, its type, allowed enum values, and the `set:` field names for `opentrain_update_job_draft_fields`.
| Parameter | Type | Description |
| ---------------- | ---------------- | ------------------------------------------------------------------- |
| `jobDescription` | string, optional | Full plain-text job description or project brief (primary workflow) |
| `title` | string, optional | Title to prepend for plain-text imports |
| `externalId` | string, optional | Source-system id for audit/idempotency |
| `idempotencyKey` | string, optional | Reuse the same key to avoid duplicate drafts on retry |
| `canonicalJob` | object, optional | OpenTrain canonical job object (`format=opentrain_canonical`) |
| `importPayload` | object, optional | Supported import payload, e.g. schema.org JSON-LD |
**Requires:** `jobs:write` + the `public_api_job_drafting` feature. **Wraps:** [`POST /job-drafts`](/docs/developers/api-reference/job-drafts/create)
### opentrain\_update\_job\_draft\_fields
Fill in or correct fields on an existing unpublished draft. Ask the human each missing field's `ask:` question, then patch the answers using the `set:` field names — never ask the human to author raw JSON. This tool never publishes; use `opentrain_publish_job`.
| Parameter | Type | Description |
| --------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |
| `jobId` | string, required | Existing unpublished draft job id |
| `patch` | object, required | Field patch keyed by OpenTrain field names, e.g. `{"experienceLevel": "INTERMEDIATE", "pricePerHour": 12}` |
**Requires:** `jobs:write` + the `public_api_job_drafting` feature. **Wraps:** [`PATCH /job-drafts/{jobId}`](/docs/developers/api-reference/job-drafts/update)
### opentrain\_publish\_job
Publish a draft live on the marketplace. Runs the same validation and moderation pipeline as the in-app publish flow and is subject to per-account daily publish limits. Returns the live job URL.
| Parameter | Type | Description |
| --------- | ---------------- | ----------------------------------- |
| `jobId` | string, required | Unpublished draft job id to publish |
**Requires:** `jobs:write` + the `public_api_job_publishing` feature. **Wraps:** [`POST /jobs/{id}/publish`](/docs/developers/api-reference/jobs/publish)
### opentrain\_update\_published\_job
Update fields on a live published (`OPEN`) job. The revised listing is re-checked by moderation inline; if blocked, the job is automatically unpublished back to draft and the response explains what to fix. For unpublished drafts use `opentrain_update_job_draft_fields` instead.
| Parameter | Type | Description |
| --------- | ---------------- | -------------------------------------------------------------- |
| `jobId` | string, required | Published (`OPEN`) job id |
| `patch` | object, required | Field patch, same shape as `opentrain_update_job_draft_fields` |
**Requires:** `jobs:write` + the `public_api_job_publishing` feature. **Wraps:** [`PATCH /jobs/{id}`](/docs/developers/api-reference/jobs/update)
### opentrain\_close\_job
Close (archive) a published job so it stops accepting proposals and leaves public listings. Existing contracts are unaffected. Idempotent — closing an already-archived job reports `alreadyClosed`.
| Parameter | Type | Description |
| --------- | ---------------- | ------------------------- |
| `jobId` | string, required | Published job id to close |
**Requires:** `jobs:write` + the `public_api_job_publishing` feature. **Wraps:** [`POST /jobs/{id}/close`](/docs/developers/api-reference/jobs/close)
### opentrain\_list\_jobs
List the account's own jobs (all statuses, newest first) with pagination.
| Parameter | Type | Description |
| --------- | ---------------- | ---------------------------------------------------------------------------------- |
| `status` | string, optional | Filter: `DRAFT`, `OPEN`, `ONGOING`, `COMPLETED`, `ARCHIVED`, or `PENDING_APPROVAL` |
| `cursor` | string, optional | Pagination cursor |
| `limit` | number, optional | Max jobs to return, 1–100 (default 25) |
**Requires:** `jobs:read`. **Wraps:** [`GET /jobs/mine`](/docs/developers/api-reference/jobs/mine)
### opentrain\_search\_jobs
Search the public OpenTrain job marketplace — useful for calibrating rates and seeing how similar work is scoped before posting.
| Parameter | Type | Description |
| ---------- | ---------------- | ---------------------- |
| `q` | string, optional | Free-text search query |
| `category` | string, optional | Category filter |
| `language` | string, optional | Language filter |
| `country` | string, optional | ISO country code |
| `payType` | string, optional | Payment type filter |
| `limit` | number, optional | Max results, 1–50 |
| `cursor` | string, optional | Pagination cursor |
**Requires:** nothing — tokenless public read (120 requests/minute per IP). **Wraps:** [`GET /jobs`](/docs/developers/api-reference/jobs/search)
## Proposals and Candidates
### opentrain\_list\_proposals
List proposals/candidates for a job with statuses, bids, AI interview scores, and resume match scores — the primary tool for reviewing and ranking who to hire. Use `opentrain_get_proposal` for one candidate in depth.
| Parameter | Type | Description |
| --------- | ---------------- | ------------------------------------------------------------------ |
| `jobId` | string, required | Job id to list proposals for |
| `status` | string, optional | Filter such as `UNREVIEWED`, `SHORTLISTED`, `HIRED`, or `DECLINED` |
| `cursor` | string, optional | Pagination cursor |
| `limit` | number, optional | Max proposals, 1–100 (default 25) |
**Requires:** `proposals:read`; the job must be yours. **Wraps:** [`GET /jobs/{id}/proposals`](/docs/developers/api-reference/jobs/list-proposals)
### opentrain\_get\_proposal
Read a full proposal/candidate evaluation: status, bid, masked candidate, AI-interview score and summary, location/identity verification, labeling assessment, and contract state.
| Parameter | Type | Description |
| ------------------ | ----------------- | ------------------------------------------------ |
| `proposalId` | string, required | Proposal id to read |
| `includeInterview` | boolean, optional | Also fetch the sanitized AI-interview transcript |
**Requires:** `proposals:read`; the proposal must be on a job you own. **Wraps:** [`GET /proposals/{proposalId}`](/docs/developers/api-reference/proposals/get) (+ [`GET /proposals/{proposalId}/interview`](/docs/developers/api-reference/proposals/interview) when `includeInterview` is true)
### opentrain\_get\_freelancer\_profile
Read a masked AI trainer profile for candidate evaluation: title, bio, skills, stats (earned, billed hours, job success), work and labeling experience, education, reviews, and languages. Names are masked to first name + last initial pre-hire, and personal contact details are never returned — see [Privacy and Work Email](/docs/developers/concepts/privacy-and-work-email).
| Parameter | Type | Description |
| ---------- | ---------------- | ----------------------------------------- |
| `idOrSlug` | string, required | AI trainer user id or public profile slug |
**Requires:** `proposals:read`. **Wraps:** [`GET /freelancers/{idOrSlug}`](/docs/developers/api-reference/freelancers/get)
### opentrain\_invite\_freelancer
Invite an AI trainer to a published job, creating a proposal they can respond to. Idempotent: re-inviting the same person returns the existing proposal with `alreadyInvited: true`.
| Parameter | Type | Description |
| -------------- | ---------------- | ---------------------------- |
| `jobId` | string, required | Published job id |
| `freelancerId` | string, required | AI trainer user id to invite |
**Requires:** `proposals:write` + the `public_api_hiring` feature + a **claimed** account. **Wraps:** [`POST /jobs/{id}/invites`](/docs/developers/api-reference/jobs/invite)
### opentrain\_hire\_proposal
Request a hire from a proposal. **Never hires anyone or moves money** — records a pending approval (`type: "proposal_hire"`) and returns `202` with an `approvalUrl` a signed-in human must confirm in the OpenTrain app. On confirm, OpenTrain creates the contract and funds the first escrow milestone (the human picks card or credits). A `409` with `details.reason: "payment_method_required"` includes a `billingUrl`; `not_fit_confirmation_required` means retry with `confirmNotFitOverride: true` if intentional; `already_accepted` means the proposal was already hired. Re-requesting with the same terms returns the same pending approval.
| Parameter | Type | Description |
| ----------------------- | ----------------- | --------------------------------------------------------------------------------- |
| `proposalId` | string, required | Proposal id to hire from |
| `milestone` | object, required | First escrow milestone: `{name?, description?, amount (USD, required), dueDate?}` |
| `confirmNotFitOverride` | boolean, optional | Confirm hiring a proposal previously marked "Not a fit" |
**Requires:** `proposals:write` + the `public_api_hiring` feature + a **claimed** account + a payment method or covering [credit balance](/docs/developers/concepts/credits-and-billing). **Wraps:** [`POST /proposals/{proposalId}/hire`](/docs/developers/api-reference/proposals/hire)
## Messages
### opentrain\_read\_messages
Read authorized conversation summaries or messages. Omit `conversationId` to list summaries; provide it to read messages. Never creates conversations or sends messages.
| Parameter | Type | Description |
| ---------------- | -------------------------------------- | ------------------------------------------------------------------ |
| `conversationId` | string, optional | Conversation to read; omit to list summaries |
| `cursor` | string, optional | Pagination cursor |
| `limit` | number, optional | Max records, 1–100 |
| `direction` | `older` \| `newer`, optional | Message pagination direction (with `conversationId`) |
| `filter` | `all` \| `job` \| `proposal`, optional | Summary filter (without `conversationId`) |
| `unreadOnly` | boolean, optional | Only conversations with unread messages (without `conversationId`) |
**Requires:** `messages:read`; you only see conversations you participate in. **Wraps:** [`GET /messages`](/docs/developers/api-reference/messages/list)
### opentrain\_send\_message
Send a plain-text message into an existing conversation the token owner participates in. Runs the same membership, rate-limit, and content-policy checks as the in-app messaging flow. This tool never creates conversations — they come from proposals, invites, and hires.
| Parameter | Type | Description |
| ---------------- | ---------------- | ------------------------------------- |
| `conversationId` | string, required | Existing conversation id |
| `content` | string, required | Plain-text message (max 10,000 chars) |
**Requires:** `messages:write` + the `public_api_messaging_writes` feature + a **claimed** account. **Wraps:** [`POST /messages`](/docs/developers/api-reference/messages/send)
### opentrain\_start\_proposal\_conversation
Start (or fetch) the pre-hire direct-message thread for a proposal so you can message the candidate before hiring. Idempotent get-or-create; returns the `conversationId` to use with `opentrain_send_message`. Employer side only.
| Parameter | Type | Description |
| ------------ | ---------------- | ---------------------------------------- |
| `proposalId` | string, required | Proposal to open the pre-hire thread for |
**Requires:** `messages:write` + the `public_api_messaging_writes` feature + a **claimed** account. **Wraps:** [`POST /proposals/{proposalId}/conversation`](/docs/developers/api-reference/proposals/conversation)
## Contracts and Milestones
### opentrain\_list\_contracts
List contracts (hired AI trainers), each with milestones, the post-hire job DM `conversationId`, and the masked freelancer identity (first name + last initial).
| Parameter | Type | Description |
| --------- | ----------------------------- | ---------------------------- |
| `jobId` | string, optional | Filter to one job |
| `status` | `active` \| `ended`, optional | Status filter; omit for both |
**Requires:** `payments:read`. **Wraps:** [`GET /contracts`](/docs/developers/api-reference/contracts/list)
### opentrain\_get\_contract
Read one contract in detail: status, milestones with funding/approval state, AI trainer identity, the post-hire job DM `conversationId`, and the `budget` snapshot (funded vs consumed, state `OK`/`LOW`/`DEPLETED`).
| Parameter | Type | Description |
| ------------ | ---------------- | ------------------- |
| `contractId` | string, required | Contract id to read |
**Requires:** `payments:read`. **Wraps:** [`GET /contracts/{contractId}`](/docs/developers/api-reference/contracts/get)
### opentrain\_create\_milestone
Create a new unfunded milestone on an existing contract. No money moves at creation — use `opentrain_request_milestone_funding` afterwards.
| Parameter | Type | Description |
| ------------- | ---------------- | ------------------------------------------------------ |
| `contractId` | string, required | Contract to add the milestone to |
| `description` | string, required | Description of the work to be delivered |
| `name` | string, optional | Short milestone name |
| `amountUsd` | number, optional | Milestone amount in USD |
| `volume` | number, optional | Unit volume (e.g. label count) for per-unit milestones |
| `dueDate` | string, optional | Due date (ISO 8601) |
**Requires:** `payments:write` + the `public_api_payments_write` feature + a **claimed** account. **Wraps:** [`POST /contracts/{contractId}/milestones`](/docs/developers/api-reference/contracts/create-milestone)
### opentrain\_request\_milestone\_funding
Request escrow funding for a milestone. This does **not** move money: it returns a pending approval with an `approvalUrl` that a signed-in human must open and confirm (expires in \~72h) — the [co-sign pattern](/docs/developers/concepts/human-approvals). Watch `opentrain_poll_updates` for `approval.confirmed`, or re-check with `opentrain_get_approval`.
| Parameter | Type | Description |
| ------------- | ---------------- | ----------------- |
| `milestoneId` | string, required | Milestone to fund |
**Requires:** `payments:write` + the `public_api_payments_write` feature + a **claimed** account + a payment method on file. **Wraps:** [`POST /milestones/{milestoneId}/fund`](/docs/developers/api-reference/milestones/fund)
### opentrain\_request\_milestone\_approval
Request approval (payment release) of a funded milestone. Like funding, this only creates a pending human approval with an `approvalUrl` (\~72h expiry) — no money moves until the human confirms.
| Parameter | Type | Description |
| ------------- | ---------------- | --------------------------------------------- |
| `milestoneId` | string, required | Funded (`ACTIVE_FUNDED`) milestone to release |
**Requires:** `payments:write` + the `public_api_payments_write` feature + a **claimed** account. **Wraps:** [`POST /milestones/{milestoneId}/approve`](/docs/developers/api-reference/milestones/approve)
### opentrain\_get\_approval
Check the status of a pending human co-sign approval (milestone funding, release, or contract end): `pending`, `confirmed`, `declined`, or `expired`, plus the execution result once confirmed.
| Parameter | Type | Description |
| ------------ | ---------------- | ------------------------------------------- |
| `approvalId` | string, required | Approval id from a fund/approve/end request |
**Requires:** `payments:read`. **Wraps:** [`GET /approvals/{approvalId}`](/docs/developers/api-reference/approvals/get)
### opentrain\_end\_contract
End a contract. With no funded milestones it ends immediately; if funded escrow is at stake, the call instead returns a pending approval with an `approvalUrl` a human must confirm — no funds move until then.
| Parameter | Type | Description |
| ------------ | ---------------- | --------------- |
| `contractId` | string, required | Contract to end |
**Requires:** `payments:write` + the `public_api_payments_write` feature + a **claimed** account. **Wraps:** [`POST /contracts/{contractId}/end`](/docs/developers/api-reference/contracts/end)
## Credits
### opentrain\_get\_credits
Read the prepaid credit balance: available, reserved (escrow holds), and total. No parameters.
**Requires:** `payments:read` + the `public_api_credits` feature. **Wraps:** [`GET /credits`](/docs/developers/api-reference/credits/balance)
### opentrain\_list\_credit\_ledger
Page through the credit ledger — top-ups, escrow holds, hold releases, captures, refunds, and adjustments, newest first. Each entry links the related top-up, proposal, contract, or milestone ids.
| Parameter | Type | Description |
| --------- | ---------------- | --------------------------------- |
| `cursor` | string, optional | `nextCursor` from a previous page |
| `limit` | number, optional | Page size, 1–100 (default 50) |
**Requires:** `payments:read` + the `public_api_credits` feature. **Wraps:** [`GET /credits/ledger`](/docs/developers/api-reference/credits/ledger)
### opentrain\_create\_credit\_top\_up
Start a credit top-up. No money moves from this call: it returns a Stripe Checkout `checkoutUrl` that a signed-in human must open and pay (expires in \~24h). Once paid, the balance updates automatically.
| Parameter | Type | Description |
| ----------- | ---------------- | --------------------------------------------- |
| `amountUsd` | number, required | Top-up amount in USD (min \$10, max \$10,000) |
**Requires:** `payments:write` + the `public_api_credits` feature + a **claimed** account. **Wraps:** [`POST /credits/top-ups`](/docs/developers/api-reference/credits/create-top-up)
### opentrain\_get\_credit\_top\_up
Check a top-up's status: `pending` (awaiting human payment), `completed` (credits added), `canceled`, or `expired`.
| Parameter | Type | Description |
| --------- | ---------------- | ----------------------------------------------- |
| `topUpId` | string, required | Top-up id from `opentrain_create_credit_top_up` |
**Requires:** `payments:read` + the `public_api_credits` feature. **Wraps:** [`GET /credits/top-ups/{topUpId}`](/docs/developers/api-reference/credits/get-top-up)
## Updates
### opentrain\_poll\_updates
Poll the delta feed of account events (new proposals, proposal status changes, new messages, contracts, milestone changes, pending payments, confirmed approvals, budget state changes) in one cheap call instead of re-reading every resource. Payloads carry IDs only — fetch details with the matching read tool. Event visibility follows the token's scopes.
| Parameter | Type | Description |
| --------- | ---------------- | ----------------------------------------------------------- |
| `cursor` | string, optional | `nextCursor` from the previous poll; omit on the first poll |
| `limit` | number, optional | Max events, 1–200 (default 50) |
**Requires:** at least one of `proposals:read`, `messages:read`, `payments:read`. **Wraps:** [`GET /updates`](/docs/developers/api-reference/updates/poll)
## Webhooks
### opentrain\_create\_webhook
Subscribe an HTTPS URL to platform events (push instead of polling). Deliveries are signed with HMAC-SHA256 — see [Verify Webhook Signatures](/docs/developers/guides/verify-webhook-signatures). The signing secret is returned **once** in this response; store it immediately. The subscription only receives events created after it.
| Parameter | Type | Description |
| ------------ | ------------------- | ----------------------------------------------------------------------------- |
| `url` | string, required | HTTPS endpoint that will receive signed deliveries |
| `eventTypes` | string\[], required | Event types to subscribe to, e.g. `["proposal.received", "message.received"]` |
**Requires:** `webhooks:manage` + the `public_api_webhooks` feature + the matching read scope for each subscribed event type. **Wraps:** [`POST /webhooks`](/docs/developers/api-reference/webhooks/create)
### opentrain\_list\_webhooks
List all webhook subscriptions with URL, event types, and status. Signing secrets are never included. No parameters.
**Requires:** `webhooks:manage` + the `public_api_webhooks` feature. **Wraps:** [`GET /webhooks`](/docs/developers/api-reference/webhooks/list)
### opentrain\_get\_webhook
Read one webhook subscription: URL, event types, status (`ACTIVE` or `DISABLED`), and failure/disable details. The signing secret is never returned — if lost, delete and re-create the subscription.
| Parameter | Type | Description |
| ----------- | ---------------- | ----------------------- |
| `webhookId` | string, required | Webhook subscription id |
**Requires:** `webhooks:manage` + the `public_api_webhooks` feature. **Wraps:** [`GET /webhooks/{webhookId}`](/docs/developers/api-reference/webhooks/get)
### opentrain\_delete\_webhook
Delete a webhook subscription and stop its deliveries. This is also how a `DISABLED` subscription is resumed: delete it and create a new one (which mints a new secret).
| Parameter | Type | Description |
| ----------- | ---------------- | --------------------------------- |
| `webhookId` | string, required | Webhook subscription id to delete |
**Requires:** `webhooks:manage` + the `public_api_webhooks` feature. **Wraps:** [`DELETE /webhooks/{webhookId}`](/docs/developers/api-reference/webhooks/delete)
## Tokens
### opentrain\_list\_tokens
List the account's API tokens with masked previews, scopes, status, and last-used timestamps — secrets are never shown. No parameters.
**Requires:** any valid token — token management needs no specific scope or feature flag. **Wraps:** [`GET /tokens`](/docs/developers/api-reference/tokens/list)
### opentrain\_revoke\_token
Revoke an API token by id. Irreversible — the token stops working immediately.
Revoking the token the MCP server itself is using breaks every subsequent call. Mint a replacement over HTTP with [`POST /tokens`](/docs/developers/api-reference/tokens/create) and switch to it first.
| Parameter | Type | Description |
| --------- | ---------------- | ------------------------- |
| `tokenId` | string, required | Id of the token to revoke |
**Requires:** any valid token — token management needs no specific scope or feature flag. **Wraps:** [`DELETE /tokens/{tokenId}`](/docs/developers/api-reference/tokens/revoke)
## Team
### opentrain\_get\_team
Read the employer team: organization, members with roles, and pending email invites. No parameters.
**Requires:** `team:read` + the `public_api_team` feature. **Wraps:** [`GET /team`](/docs/developers/api-reference/team/list)
### opentrain\_invite\_team\_member
Invite a human to the employer team by email, giving them shared access to jobs and the team inbox once they accept. Inviting an existing member returns `already_member`; users with existing accounts are added directly (`member_added`).
| Parameter | Type | Description |
| --------- | ---------------- | ---------------------------- |
| `email` | string, required | Email of the human to invite |
**Requires:** `team:write` + the `public_api_team` feature + a **claimed** account. **Wraps:** [`POST /team/invites`](/docs/developers/api-reference/team/invite)
## Payments
### opentrain\_list\_pending\_payments
List pending payments on the account — read-only; never releases funds. No parameters.
**Requires:** `payments:read`. **Wraps:** [`GET /payments/pending`](/docs/developers/api-reference/payments/pending)