REST API
Call Upwork Job Search MCP tools directly from any HTTPS client. The REST surface is a thin wrapper over the same handlers the MCP server uses — billing, rate limits, and analytics are identical.
https://api.getmany.com.uaWhen to use REST instead of MCP
- You're wiring up a cron job, a Zapier/Make/n8n workflow, an internal dashboard, or a one-off script. No LLM involved.
- You want a single deterministic HTTP request per tool call — no tool discovery, no protocol negotiation, no streaming.
- Your runtime is an HTTP-only environment — Edge Functions, BI tools, spreadsheet add-ons, webhooks.
If you're building an agent that needs tool discovery, sampling, or multi-tool orchestration, stick with the MCP transport — see /docs/setup.
Authentication
Every request must carry a Bearer token in the Authorization header. Keys are issued from your dashboard and start with the gm_ prefix. The key is shown once on creation — store it in a secret manager.
Authorization: Bearer gm_your_api_key_hereAll requests must send Content-Type: application/json.
Full key-management guidance — rotation, revocation, scope — lives in /docs/authentication.
Endpoints
All endpoints accept POST only (other methods return 405). The request body is the tool input directly — there is no JSON-RPC envelope, no method field, no params wrapper. Full per-tool input schemas (every filter, every flag) are in /docs/tools.
POST /v1/search_jobs
Search Upwork jobs with 40+ filters across keywords, budget, client requirements, and vendor preferences. Returns up to 10,000 rows per call.
{
"limit": 100,
"searchPeriod": "24 hours",
"jobCategories": ["Web Development"],
"budget.hourlyRate.min": "50"
}POST /v1/get_job
Fetch full details for a single job by UID — description, skills, budget, client stats, and application cost.
{
"jobUid": "1955020056847176693",
"withClientHistory": false
}POST /v1/get_client_activity
Premium addon. Proposal count, last client activity, interviewing candidates, invites sent, and unanswered invites for a given job.
{ "jobUid": "1955020056847176693" }POST /v1/get_client_history
Premium addon. The client's work history and contractor feedback from previous freelancers on the same job posting.
{ "jobUid": "1955020056847176693" }Full examples
Three copy-paste examples for search_jobs — pick the language of your runtime.
cURL
curl -X POST https://api.getmany.com.ua/v1/search_jobs \
-H "Authorization: Bearer gm_your_api_key_here" \
-H "Content-Type: application/json" \
-d 39;{
"limit": 100,
"searchPeriod": "24 hours",
"jobCategories": ["Web Development"],
"budget.hourlyRate.min": "50"
}39;Node.js / TypeScript
const response = await fetch("https://api.getmany.com.ua/v1/search_jobs", {
method: "POST",
headers: {
"Authorization": "Bearer gm_your_api_key_here",
"Content-Type": "application/json"
},
body: JSON.stringify({
limit: 100,
searchPeriod: "24 hours",
jobCategories: ["Web Development"],
"budget.hourlyRate.min": "50"
})
});
const { data, usage } = await response.json();
console.log(data);Python
import requests
response = requests.post(
"https://api.getmany.com.ua/v1/search_jobs",
headers={
"Authorization": "Bearer gm_your_api_key_here",
"Content-Type": "application/json"
},
json={
"limit": 100,
"searchPeriod": "24 hours",
"jobCategories": ["Web Development"],
"budget.hourlyRate.min": "50",
}
)
body = response.json()
print(body["data"])And the same pattern for get_job — just swap the path and body shape.
cURL — get_job
curl -X POST https://api.getmany.com.ua/v1/get_job \
-H "Authorization: Bearer gm_your_api_key_here" \
-H "Content-Type: application/json" \
-d 39;{ "jobUid": "1955020056847176693" }39;Node.js / TypeScript — get_job
const response = await fetch("https://api.getmany.com.ua/v1/get_job", {
method: "POST",
headers: {
"Authorization": "Bearer gm_your_api_key_here",
"Content-Type": "application/json"
},
body: JSON.stringify({ jobUid: "1955020056847176693" })
});
const { data } = await response.json();
console.log(data);Response shape
Successful calls return HTTP 200 with a top-level data field carrying the tool result and a usage field carrying per-call credit metering.
{
"data": {
"jobs": [
{
"uid": "1955020056847176693",
"title": "Senior React Developer for SaaS",
"hourlyRate": 65,
"createdAt": "2026-05-27T10:30:00.000Z"
}
],
"total": 42
},
"usage": {
"consumedMicrocents": 5790,
"remainingMicrocents": 24210000
}
}Errors return a non-2xx status with error (human message) and error_code (machine-readable enum). Switch on error_code — the message is for humans and may change.
{
"error": "Invalid jobUid",
"error_code": "validation_error"
}Error codes
| error_code | HTTP | Meaning |
|---|---|---|
validation_error | 400 | Malformed JSON body, missing required field (e.g. jobUid), or an input that failed schema validation. |
invalid_key | 401 | Missing, malformed, or unknown Bearer token. Re-issue a key from the dashboard. |
insufficient_credits | 402 | Your monthly pool plus any prepaid credit packs are empty. Top up a pack or wait for the next renewal — no metered overage. |
addon_forbidden | 403 | The tool is a premium addon and your plan does not include it. Upgrade to Pro to call get_client_activity or get_client_history. |
method_not_allowed | 405 | Tool paths only accept POST. Any other method returns 405 with an Allow: POST header. |
payload_too_large | 413 | Request body exceeded the per-call size cap (10 MiB). Trim filters, split into multiple calls, or set a tighter limit. |
rate_limited | 429 | Per-API-key rate limit hit. Honour the Retry-After header (seconds) before retrying. |
runtime_error | 500 | Internal server fault. Rare; retry with exponential backoff. The message is intentionally generic — server-side logs carry the detail. |
Rate limits and quota
Rate limits are enforced per API key. When you exceed the window the server returns HTTP 429 with a Retry-After header (seconds). Honour it — retry sooner and you'll just be rejected again.
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
{
"error": "rate limited",
"error_code": "rate_limited",
"retryAfterMs": 60000
}When your monthly credit pool plus any prepaid credit packs are exhausted, the server returns HTTP 402 with error_code: insufficient_credits and a hint pointing to the dashboard. There is no metered overage — you top up a pack or wait for the next renewal.
HTTP/1.1 402 Payment Required
Content-Type: application/json
{
"error": "insufficient_credits",
"error_code": "insufficient_credits",
"message": "Out of credits — buy a credit pack (or enable auto-refill) to keep going.",
"neededMicrocents": 5790,
"availableMicrocents": 0,
"upgradeUrl": "https://app.getmany.com.ua/dashboard?upgrade=pro",
"hint": "Buy a credit pack at /dashboard"
}Full rate-limit windows, quota math, and credit-pack pricing live in /docs/rate-limits and /docs/billing.