UpHunt API
API Endpoints

Get Job

Fetch a single archived Upwork job by its ciphertext slug.

GET
/api/jobs/by-ciphertext

Returns the full archived RawUpworkJob document for one Upwork job, identified by its ciphertext slug (the ~02xxx... portion of an Upwork job URL).

The legacy /api/v1/jobs/:ciphertext and /api/v2/jobs/:ciphertext paths still work — they forward to this endpoint and behave identically (same x-api-key auth).

Authentication

This endpoint uses the same x-api-key header as the auto-apply endpoints — the key scoped to your UpHunt account. Generate it from Dashboard → Settings → API.

x-api-key: <your-key>

Query parameters

ParameterTypeDescription
cipherstringrequired

Upwork job ciphertext, e.g. ~022053140178050136031. URL-encode the leading tilde (%7E).

Example

cURL
curl "https://uphunt.io/api/jobs/by-ciphertext?cipher=~022053140178050136031" -H "x-api-key: YOUR_API_KEY"

Response

The full RawUpworkJob document is returned: top-level fields like title, description, ontologySkills[], jobTile.job.*, plus optional nested jobDetailsResponse (public details: budget, qualifications, buyer location/stats) and authJobDetailsResponse (auth-only enrichment: screening questions, attachments, buyer work history, applicant bid stats). When the auth-enriched scrape captured screening questions, they're also surfaced at the top level as screeningQuestions so you can pass position values directly to the apply endpoint's questionAnswers.

Three more fields carry what the UpHunt job view shows:

  • proposalActivity - live competition data. proposals, interviewing, hired, invitesSent, unansweredInvites, positionsToHire, lastClientActivity, a competition level (low / medium / high), and bidStats (avg, min, max bid). source is tracker when our proposal tracker is watching the job (then tracking and a history of change points are included) and scrape when the counts come from our first scrape. A null count means Upwork has not published it yet, not zero. The whole field is null when we have no data.
  • clientScore - our client quality score: score (0 to 100, null for a new client), label, four components (hires, money, reputation, engagement) with plain facts, plus flags and perks.
  • matches - every time this job matched one of your feeds: processedJobId, feedId, matchingScore, matchedAt, and application (status, applied time, and whether the client viewed your proposal).
{
  "id": "2053140178050136031",
  "title": "LONG-TERM! Only Experienced Video Editor for YTA Channel",
  "description": "Are YOU an experienced video editor for YouTube automation channels?...",
  "ontologySkills": [
    { "uid": "1031626793223176192", "prefLabel": "Video Editing" }
  ],
  "jobTile": {
    "job": {
      "id": "2053140178050136031",
      "ciphertext": "~022053140178050136031",
      "jobType": "HOURLY",
      "publishTime": "2026-05-09T15:53:59.413Z"
    }
  },
  "exploredAt": 1778344861000,
  "screeningQuestions": [
    { "position": 0, "question": "How many years of React experience do you have?" },
    { "position": 1, "question": "Have you worked on a similar project before?" }
  ],
  "jobDetailsResponse": { "data": { "jobPubDetails": { /* ... */ } } },
  "authJobDetailsResponse": { "data": { "jobAuthDetails": { /* ... */ } } },
  "proposalActivity": {
    "source": "tracker",
    "proposals": 14,
    "interviewing": 2,
    "hired": 0,
    "invitesSent": 5,
    "unansweredInvites": 1,
    "positionsToHire": 1,
    "lastClientActivity": "2026-10-04T10:00:00Z",
    "competition": "medium",
    "bidStats": { "avg": 35, "min": 15, "max": 80, "currency": "USD" },
    "tracking": {
      "active": true,
      "stoppedReason": null,
      "openingStatus": "ACTIVE",
      "startedAt": "2026-10-04T17:28:37.866Z",
      "lastCheckedAt": "2026-10-04T19:28:37.866Z",
      "checks": 12,
      "proposalsLastHour": 6,
      "proposalsAtStart": 3,
      "peakProposals": 14
    },
    "history": [
      { "at": "2026-10-04T17:28:37.866Z", "proposals": 3, "interviewing": 0, "hired": 0 },
      { "at": "2026-10-04T18:58:37.866Z", "proposals": 14, "interviewing": 2, "hired": 0 }
    ]
  },
  "clientScore": {
    "score": 72,
    "label": "good",
    "components": { "hires": { "score": 80, "facts": ["hires on 4 of 5 posts"] } /* money, reputation, engagement */ },
    "flags": [],
    "perks": ["hires almost every post"],
    "facts": ["hires on 4 of 5 posts"]
  },
  "matches": [
    {
      "processedJobId": "6b1f...",
      "feedId": "a3c9...",
      "matchingScore": 8,
      "matchedAt": "2026-10-04T17:30:02.000Z",
      "read": true,
      "starredAt": null,
      "application": {
        "status": "applied",
        "message": null,
        "appliedAt": "2026-10-04T17:41:10.000Z",
        "proposal": { "viewedByClient": true, "viewedAt": null, "inConversation": false, "outcome": null, "outcomeAt": null, "lastCheckedAt": "2026-10-04T19:00:00.000Z" }
      }
    }
  ]
}

Errors

Our archive goes back several years, but very old or never-scraped jobs may not be present.

StatusWhen
400Missing cipher query parameter
401x-api-key missing or invalid
404Ciphertext doesn't match any archived job

On this page