UpHunt API

Recipes

Ready-to-adapt snippets for common UpHunt workflows.

Copy, paste, adapt. Every snippet below solves a real integration problem.

Apply only when the client is verified

Skip jobs from unverified clients by inspecting the webhook payload before calling /apply.

app.post('/webhook/uphunt', async (req, res) => {
  const job = req.body
 
  if (!job.client?.paymentMethodVerified) return res.json({ skipped: true })
  if (job.matchingScore < 7) return res.json({ skipped: true })
 
  const coverLetter = await generateCover(job)
  const { queueId } = await applyV2(job.ciphertext, coverLetter)
  res.json({ queueId })
})

Apply with the right specialized profile

Route React jobs to the "React Developer" profile, Python jobs to the "Backend Engineer" profile.

const freelancers = await fetch('https://uphunt.io/api/auto-apply/external/freelancers', {
  headers: { 'x-api-key': process.env.UPHUNT_API_KEY! },
}).then(r => r.json())
 
const profilesByTag: Record<string, string> = {}
for (const f of freelancers.freelancers) {
  for (const p of f.profiles) {
    if (p.name.toLowerCase().includes('react')) profilesByTag.react = p.profileId
    if (p.name.toLowerCase().includes('python')) profilesByTag.python = p.profileId
  }
}
 
function pickProfile(skills: { name: string }[]) {
  const names = skills.map(s => s.name.toLowerCase())
  if (names.some(n => n.includes('react'))) return profilesByTag.react
  if (names.some(n => n.includes('python'))) return profilesByTag.python
  return undefined
}

Bid higher on enterprise-tier jobs

function proposalFor(job: any) {
  const base = job.contractorTier === 'Expert' ? 85 : 55
  const hourlyMax = job.budget?.hourlyMax ?? base
  return {
    hourlyRate: Math.min(hourlyMax, Math.round(base * 1.1)),
    timeline: '1 to 3 months',
    boostBids: job.contractorTier === 'Expert' ? 5 : 0,
  }
}

Daily digest of applied jobs

Send a Slack message every morning with yesterday's activity.

import { WebClient } from '@slack/web-api'
const slack = new WebClient(process.env.SLACK_TOKEN)
 
const since = Date.now() - 24 * 60 * 60 * 1000
const { jobs } = await fetch(
  'https://uphunt.io/api/auto-apply-v2/applied-jobs?limit=200&status=applied',
  { headers: { 'x-api-key': process.env.UPHUNT_API_KEY! } },
).then(r => r.json())
 
const yesterday = jobs.filter((j: any) => j.appliedAt >= since)
const text = `*UpHunt daily*\n${yesterday.length} applications sent in the last 24h.\n\n` +
  yesterday.map((j: any) => `• <${j.url}|${j.title}>`).slice(0, 10).join('\n')
 
await slack.chat.postMessage({ channel: '#freelance', text })

Retry logged_out with backoff

logged_out is transient — UpHunt reconnects the business developer automatically. Retry the same jobId after a short delay.

const status = await checkStatus(queueId)
 
if (status.applicationStatus === 'logged_out') {
  // Wait for the background reconnect, then retry.
  await new Promise(r => setTimeout(r, 30_000))
  await apply({ jobId: status.jobId, coverLetter })
}

Deduplicate matches across feeds

If the same job can hit multiple feeds, skip duplicates with a 30-day TTL Redis set.

import Redis from 'ioredis'
const redis = new Redis(process.env.REDIS_URL!)
 
app.post('/webhook/uphunt', async (req, res) => {
  const job = req.body
  const key = `applied:${job.ciphertext}`
  const isNew = await redis.set(key, '1', 'EX', 30 * 24 * 60 * 60, 'NX')
  if (!isNew) return res.json({ deduped: true })
 
  // ... apply flow
})

n8n workflow

For no-code automation, see UpHunt + n8n — a full visual workflow you can import.

On this page