# Toma API reference — jobs, teams, MCP, and health

> Documentation for the Toma API: open jobs, company teams, job posting, API keys, the MCP endpoint, the health check, and the OpenAPI contract.

Every job on Toma was posted by a signed-in company. Background checks and payments are not available yet.

## Base URL

Use the same origin as this documentation, over HTTPS on the public deployment. All routes are under /api. Responses are JSON with Cache-Control: no-store. No SDK is required.

## GET /api/health

Returns HTTP 200 with Content-Type: application/json and body {"status":"ok"}. Responses use Cache-Control: no-store. No parameters, request body, or authentication are required.

## HEAD /api/health

Returns HTTP 200 with the same response headers and no body.

## Authentication

REST: a human signs in on the website and creates an API key under company settings. Send it as Authorization: Bearer <key>. Keys start with tl_ and act as that human inside their team. Each key allows 120 requests per minute. Browser sessions also work for same-origin JSON requests. MCP does not accept API keys: MCP clients sign in with OAuth 2.1 (authorization code with PKCE, dynamic client registration). Discovery: /.well-known/oauth-protected-resource/api/mcp and /.well-known/oauth-authorization-server.

## GET /api/jobs

Public. Returns {"jobs":[...]} with open jobs, newest first. Each job has id, company, title, budget in whole US dollars, constraints, status, and createdAt. Optional query: limit, from 1 to 200, default 100.

## GET /api/jobs/{id}

Public. Returns one open job, or 404 when the id is unknown or the job is closed. Same as the get_job MCP tool. Opening a job counts as a view in its company's performance report.

## GET /api/team/performance

Company accounts. Query: days (7, 30, or 90; default 30). Returns totals for the range and for the equal period before it (impressions, views, viewRate, applications, applyRate, agentShare), daily totals split by agents and people, one row per job with dailyViews, and the definitions used. An impression is a job returned in a list or search result; a view is a job's details opened. Each viewer counts once per job per UTC day; the team's own members and search crawlers are excluded. Same as the get_job_performance MCP tool.

## GET /api/jobs/search

Public. Finds open jobs by meaning and keywords. Query: q (plain English, up to 300 characters, may include a budget or ordering such as "research under $200" or "highest paying design"), min_budget and max_budget (inclusive whole dollars), sort (relevance, newest, budget_desc, budget_asc), and limit (1 to 50, default 20). Explicit parameters override what q implies. Returns {"jobs":[...],"interpretation":{"query","minBudget","maxBudget","sort","interpreted"},"mode"}. mode is hybrid (semantic and full-text index), keyword (word-match fallback), or filter (no topic, filters only). The search_jobs MCP tool takes the same arguments, with query in place of q.

## GET and PATCH /api/team

GET returns the caller's team, role, members, and jobs. PATCH with {"name":"..."} renames the company and needs the owner or admin role. Errors return {"error":"...","fields":{...}} with HTTP 400, 401, or 403.

## GET and POST /api/team/jobs

GET lists the team's jobs. POST with {"title","budget","constraints","acceptListingFee":true} publishes an open job and returns HTTP 201. Every posting costs a $1 listing fee; acceptListingFee must be true or the request fails with HTTP 400. The fee is recorded, not charged, until payments are available. Titles are one line, all lowercase, and at most 120 characters. Budgets are whole dollars from 1 to 1,000,000. Constraints are at most 4,000 characters.

## Candidate routes

Candidate accounts only. GET /api/candidate returns the profile and verification status. PUT /api/candidate/profile saves the profile; the state must be a US state or DC, and usWorkAuthorized must be true. GET /api/candidate/verification returns the background check and employment verification status.

Background checks are not available yet: the status is "unavailable" and POST /api/candidate/verification returns HTTP 503. When a screening provider is connected, the check will start only from the candidate's own browser session, because it records their consent.

## Applications

Candidates: POST /api/jobs/{id}/applications with {"pitch","agent"} applies to an open job (HTTP 201). It needs a saved profile and allows one application per job. GET /api/candidate/applications lists your applications. POST /api/candidate/applications/{id}/withdraw withdraws one that is still waiting.

Companies: GET /api/team/jobs/{id}/applications lists a job's applicants with their profile, verification status, pitch, and agent. POST /api/team/applications/{id}/accept hires one applicant: the job is filled and leaves the board and search, other waiting applicants are declined, and both sides receive each other's email. POST /api/team/applications/{id}/decline declines one. POST /api/team/jobs/{id}/close stops taking applications without hiring. Emails are shared only after acceptance. If two people accept at once, one succeeds and the other gets HTTP 409.

## POST /api/mcp

The MCP endpoint. Send one JSON-RPC 2.0 message per POST. Supports initialize, ping, tools/list, and tools/call. The tools mirror the REST operations and use the same validation.

## Errors and supported methods

GET /api/health returns {"status":"ok"}, and HEAD returns the same headers with no body. GET /api/health/ready checks the database (200 or 503) and reports which sign-in methods, search mode, and background checks are configured. Unsupported methods return HTTP 405 with an Allow header. Unknown API paths return HTTP 404. Missing or invalid keys return HTTP 401. GET /api/jobs/search allows 30 searches per minute per caller and returns HTTP 429 with Retry-After beyond that. Unexpected errors return HTTP 500 with {"error","requestId"}; every API response carries an x-request-id header to quote to support@toma.com.

## Example request

curl --fail --silent --show-error "$TOMA_URL/api/jobs?limit=10"

curl --fail --silent --show-error --get "$TOMA_URL/api/jobs/search" --data-urlencode "q=data cleanup under $300"

curl -X POST "$TOMA_URL/api/team/jobs" -H "Authorization: Bearer $TOMA_KEY" -H "Content-Type: application/json" -d '{"title":"audit our docs","budget":200,"constraints":"One week.","acceptListingFee":true}'

Set TOMA_URL to the public origin, without a trailing slash.

## Machine-readable contract

- [OpenAPI 3.1 JSON](https://board.toma.com/openapi.json)
- [Capability manifest](https://board.toma.com/agent-info.json)
- [Agent guide](https://board.toma.com/agents)

HTML: https://board.toma.com/api-docs
