Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: GitHunt Search API
version: 1.0.0
description: 'Search, look up, and analyze GitHub developers programmatically. GitHunt gives you millisecond access to millions of developers available on GitHub - ranked, scored, and with contact details where publicly available. Every request is billed against your plan''s monthly API call quota; all plans are additionally capped at 2000 calls/day.
Quotas: Free 50 calls/month (trial), Pro 1000 calls/month, Team 1500 calls/month (unused credits roll over, up to one extra month''s allowance), Enterprise unlimited. Endpoints cost different amounts of quota: search 1 credit, get user 1 credit, evidence report 3 credits (0 when the report is still pending), analyze profile 3 credits, and every /v1/lists operation 0 credits. Endpoints that debit 0 credits still count against the daily cap.'
contact:
email: support@githunt.ai
servers:
- url: https://api.githunt.ai
security:
- bearerAuth: []
- apiKeyAuth: []
tags:
- name: Search
paths:
/v1/search:
post:
operationId: searchDevelopers
summary: Search developers by location, role, skills, and languages
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SearchRequest'
responses:
'200':
description: Search results
content:
application/json:
schema:
$ref: '#/components/schemas/SearchResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/QuotaExceeded'
'500':
$ref: '#/components/responses/InternalError'
tags:
- Search
components:
schemas:
TopOssContributions:
type: array
maxItems: 3
description: Notable open-source projects the candidate contributed to
items:
type: object
properties:
repository:
type: string
description: Full repository name (owner/name)
tier_label:
type: string
description: Project prominence tier (e.g. Elite, Popular, Notable)
commits:
type: integer
language:
type: string
nullable: true
SearchResult:
type: object
properties:
login:
type: string
score:
type: number
profile_score:
type: number
tech_stack_score:
type: number
activity_score:
type: number
github_experience:
type: number
matching_keywords:
type: string
description: Comma-separated list of matched keywords
avatar_url:
type: string
email:
type: string
nullable: true
name:
type: string
nullable: true
company:
type: string
nullable: true
location:
type: string
nullable: true
followers:
type: integer
bio:
type: string
nullable: true
profile_url:
type: string
blog:
type: string
twitter_username:
type: string
hireable:
type: string
enum:
- 'Yes'
- 'No'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
last_active_date:
type: string
nullable: true
description: Date of the candidate's most recent GitHub activity. Usually a date (YYYY-MM-DD); may be a full ISO date-time when derived from fallback sources.
commits_per_month:
type: number
nullable: true
description: Average commits per month over recent history
commit_frequency_label:
type: string
nullable: true
description: Human label for commit activity (e.g. Very Active, Active, Unknown)
semantic_commit_percentage:
type: number
nullable: true
description: Percentage of analyzed commits with semantic/descriptive messages
commit_message_quality_label:
type: string
nullable: true
description: Human label for commit message quality (e.g. Excellent, Good, Insufficient Data)
top_repositories:
$ref: '#/components/schemas/TopRepositories'
top_oss_contributions:
$ref: '#/components/schemas/TopOssContributions'
evidence:
$ref: '#/components/schemas/Evidence'
org_affinity:
$ref: '#/components/schemas/OrgAffinity'
domain_evidence:
$ref: '#/components/schemas/DomainEvidence'
inferred_utc_offset:
$ref: '#/components/schemas/InferredUtcOffset'
location_resolution:
$ref: '#/components/schemas/LocationResolution'
rank_breakdown:
type: object
description: Present when the request named languages, skills, sectors, organizations or domains. Components in [0,1] of the evidence ranking formula rank = base * (1 + 0.6*recency + 0.4*org + 0.5*domain); unknown evidence scores 0 and never lowers a row.
properties:
base:
type: number
recency:
type: number
org:
type: number
domain:
type: number
final:
type: number
score_boosts:
type: object
description: 'Ranking boosts applied on top of the base score (present only when non-zero). `score` stays clamped to 0-100. When the request names languages, skills, sectors, organizations or domains, ordering is not this boosted value: results are ranked by the base score scaled by how recently the requested skills were used and how strongly the candidate matches the requested employers and domains, so a result may sit above one with a higher `score`.'
properties:
org_affinity:
type: number
domain_evidence:
type: number
ErrorResponse:
type: object
required:
- success
- error
properties:
success:
type: boolean
const: false
error:
type: object
required:
- code
- message
properties:
code:
type: string
enum:
- unauthorized
- quota_exceeded
- invalid_request
- not_found
- internal
message:
type: string
LocationResolution:
type: object
description: 'How the candidate''s stated base resolved at the run timestamp. Attached to every result whenever the request carried an exclusion or a `skillRecency` filter. Discovery stays permissive: `ambiguous` and `unresolved` candidates are still returned, and belong in a screening queue rather than a geographically eligible shortlist. Commit timezone is never `resolved` - an author-side commit offset can support discovery but cannot establish a country or a current base, and never excludes a candidate on its own - see `screening_reason`.'
properties:
status:
type: string
enum:
- resolved
- ambiguous
- unresolved
country:
type: string
nullable: true
description: Null unless the stated location settles a single country.
utc_offset:
type: object
nullable: true
description: Offset band in hours at the run timestamp, daylight saving applied. `min` equals `max` when the location pins a single zone.
properties:
min:
type: number
max:
type: number
basis:
type: string
nullable: true
enum:
- stated_location
- commit_timezone
screening_reason:
type: string
nullable: true
enum:
- inferred_offset_in_excluded_band
description: 'Why this candidate needs a screening question before being counted as geographically eligible. `inferred_offset_in_excluded_band` means the stated location did not resolve and the candidate''s commit timezone falls inside the `excludeUtcOffsets` band the request asked to exclude. Such candidates are deliberately returned rather than excluded: a commit offset cannot establish a country or a current base, so it is a question to ask, not an answer. Null when no screening flag applies.'
observed_at:
type: string
format: date-time
description: The run's frozen evaluation instant.
SearchRequest:
type: object
description: At least one of `location`, `locations`, or `regions` is required.
properties:
location:
type: string
example: Poland
role:
type: string
example: backend
skills:
type: array
items:
type: string
maxItems: 20
example:
- go
- kubernetes
languages:
type: array
items:
type: string
maxItems: 10
example:
- Go
minExperienceYears:
type: integer
minimum: 0
maximum: 50
isHireable:
type: boolean
strictSkills:
type: boolean
default: false
includeUnlocated:
type: boolean
default: false
description: When true, also search candidates indexed with no stated location. Their location is unknown, so they are returned as screening leads and can never be counted as geographically verified; `meta.coverage.unlocated_included` marks a response that contains them.
maxResults:
type: integer
minimum: 1
maximum: 100
default: 25
offset:
type: integer
minimum: 0
maximum: 200
default: 0
description: Number of top-ranked candidates to skip, for paging. Use with maxResults to fetch the next page. Consecutive pages never overlap.
excludeLogins:
type: array
items:
type: string
maxItems: 500
example:
- octocat
- torvalds
description: GitHub logins to leave out of results, e.g. candidates already seen in a prior call. Use for dedup across searches. Case-insensitive; invalid entries are dropped rather than rejected.
excludeLocations:
type: array
items:
type: string
maxItems: 50
example:
- India
description: Countries or cities to exclude, matched the same way as the location parameter (aliases and native names included).
excludeUtcOffsets:
type: object
description: Exclude candidates whose stated location resolves entirely within this UTC offset band, in hours. A location that only partially overlaps the band, or can't be resolved, is kept (not excluded) - a candidate whose commit timezone falls inside the band but whose location is unresolvable comes back flagged with `location_resolution.screening_reason`, never dropped.
properties:
min:
type: number
minimum: -12
maximum: 14
max:
type: number
minimum: -12
maximum: 14
required:
- min
- max
example:
min: 0
max: 5
locations:
type: array
items:
type: string
maxItems: 10
example:
- Berlin
- Warsaw
description: Additional cities/countries to search in one call, freeform like `location`, merged with it (max 10 combined). Live GitHub crawl only covers the first location; further locations are served from the candidate pool only. At least one of `location`, `locations`, or `regions` is required.
regions:
type: array
items:
type: string
enum:
- north-america
- latin-america
- south-america
- europe
- western-europe
- eastern-europe
- nordics
- dach
- benelux
- iberia
- uk-ireland
- middle-east
- africa
- apac
- east-asia
- southeast-asia
- south-asia
- oceania
- americas
- emea
- worldwide
maxItems: 3
example:
- apac
description: Named region shortcuts that expand to their member countries, merged with `location`/`locations` (max 10 combined locations after expansion; extras are dropped). Case-insensitive. A request whose region names are all unknown and that carries no `location` is rejected with `invalid_request`.
organizations:
type: array
items:
type: string
maxItems: 20
example:
- stripe
- adyen
description: GitHub org logins to check candidates' employer affinity against. Boosts matching candidates' rank unless `requireOrgAffinity` is true.
sectors:
type: array
items:
type: string
enum:
- adtech
- ai-ml
- banking
- cloud
- cybersecurity
- ecommerce
- fintech
- gaming
- healthtech
- media
- mobility
- payments
maxItems: 5
example:
- payments
description: Industry sectors to check candidates' employer affinity against, via GitHunt's employer graph (public GitHub orgs mapped to companies and sectors).
requireOrgAffinity:
type: boolean
default: false
description: When true, only return candidates matching one of the given `organizations`/`sectors` (hard filter) instead of just boosting their rank.
domains:
type: array
items:
type: string
enum:
- payments
- healthcare
- adtech
maxItems: 3
example:
- payments
description: Domain vocabulary packs to score candidates' repos/bio against for subject-matter evidence (e.g. core payments-processing terminology, not just the word "payments").
skillRecency:
type: array
minItems: 1
maxItems: 10
items:
$ref: '#/components/schemas/SkillRecencyEntry'
example:
- skill: Scala
usedWithinYears: 3
description: 'Filter on public skill activity. Entries are ANDed: a candidate must satisfy every entry to be returned. Only candidates whose signals positively support every entry are returned, and only they occupy a slot in the per-location selection the search scores, so a candidate with evidence is never crowded out by higher-activity candidates without it. Candidates whose signals cannot answer the question are `unknown` and are left out of the results (and queued for enrichment), which is not a negative finding - see `meta.skill_recency`. `unknown` covers a signal that is missing, failed, expired (signals are refreshed on a 30-day cycle, and one older than that no longer answers a strict filter) or partial (the years the answer depends on could not be fetched). Malformed entries are an uncharged `invalid_request`. When `skillRecency` is set, the search is served from the candidate pool alone: a live GitHub row carries no derived signals and could only ever be `unknown`.'
excludeList:
type: string
pattern: ^[a-z0-9][a-z0-9_-]{0,63}$
example: vami-scala-payments
description: 'Name of a seen list (see /v1/lists) whose logins are merged into `excludeLogins` for this search, so a rerun of the same brief never returns candidates the account already recorded. Only permanent `seen` entries and claims that have not expired are excluded: an interrupted run''s candidates are rediscoverable as soon as the claim lapses. The server-side list is bounded at 5000 eligible logins rather than the 500-entry client cap; `meta.exclude_list.truncated` reports when that bound was hit. An unknown list is a `not_found` before any quota is charged, so create the list first with an empty `POST /v1/lists/{name}/logins`.'
SkillRecencyEntry:
type: object
description: One skill-activity predicate. At least one of `minActiveYears` or `usedWithinYears` is required; both may be given.
required:
- skill
properties:
skill:
type: string
minLength: 1
maxLength: 64
example: Scala
description: Language or skill name. Common aliases resolve to the canonical GitHub Linguist name (ts, js, py, rb, kt, rs, sh, cpp, c#, golang).
minActiveYears:
type: integer
minimum: 1
maximum: 9
description: Minimum number of distinct calendar years with public activity in this skill (a year counts at five or more commits). This is a public-activity measure, never elapsed or commercial tenure.
usedWithinYears:
type: integer
minimum: 1
maximum: 9
description: Require dated activity inside the rolling window of this many years ending at the request's UTC timestamp. Calendar-year subtraction in UTC, clamping Feb 29 to Feb 28 in a non-leap target year; the cutoff day itself counts as inside the window.
SearchResponse:
type: object
properties:
success:
type: boolean
const: true
data:
type: object
properties:
matchedCount:
type: integer
description: Results in this page.
totalCount:
type: integer
offset:
type: integer
description: Echoes the applied offset.
locationSuggestions:
type: array
items:
type: string
description: Similar supported locations ("City, Country"), present only when the search returned zero results because the location matched no known city or country.
results:
type: array
items:
$ref: '#/components/schemas/SearchResult'
meta:
type: object
properties:
requestId:
type: string
dataSource:
type: string
example: candidate_pool
quota:
$ref: '#/components/schemas/Quota'
coverage:
type: object
description: What the search actually read, so an empty page is never mistaken for an exhaustive absence.
properties:
locations_searched:
type: integer
locations_dropped:
type: integer
description: Locations left unsearched because region expansion exceeded the per-request location cap.
partial:
type: boolean
description: True when retrieval was bounded (a capped partition, a per-location read cap, a timed-out location read, or a failed pool read).
candidates_fetched:
type: integer
description: Pool candidates that passed the request's exclusions across all searched locations, before ranking.
candidates_scored:
type: integer
description: Candidates handed to the ranker. Scaled with the number of locations under a per-request ceiling and the time budget, so a region search does not split one location's budget across its countries.
unlocated_included:
type: boolean
description: Present only when the request set `includeUnlocated`. Results may include candidates with no stated location, which are screening leads, not verified geography.
skill_recency:
type: object
description: Present only when the request carried `skillRecency`. Counts come from every candidate seen in the queried partitions, not from the returned page.
properties:
evaluated:
type: integer
supported:
type: integer
unknown:
type: integer
description: Candidates whose signals could not answer the filter, because they are missing, failed, expired (older than the 30-day signal refresh window) or partial (the years the answer depends on could not be fetched). Not a negative finding; they are excluded from the scored selection, and queued for enrichment so a later search can judge them on evidence.
contradicted:
type: integer
description: Candidates the signals positively rule out. A bound that only an unfetched year could have met is `unknown`, not `contradicted`.
partial:
type: boolean
description: True when retrieval was bounded (a capped partition or a failed pool read), so an empty page is not evidence of absence.
exclude_list:
type: object
description: Present only when the request carried `excludeList`.
properties:
name:
type: string
logins:
type: integer
description: Logins merged into the exclusion from the list.
truncated:
type: boolean
description: True when the list exceeded the 5000-login read bound.
DomainEvidence:
type: object
description: Present when `domains` were given, keyed by pack name (e.g. "payments").
additionalProperties:
type: object
properties:
score:
type: number
tier:
type: string
enum:
- core
- adjacent
- generic
- none
description: Strongest vocabulary tier the candidate actually matched. Ranking uses this rather than `score`, because a score sums tiers and so cannot separate several generic words from one core term.
summary:
type: string
example: 'Core: settlement, PSP; adjacent: checkout, refund'
core_terms:
type: array
items:
type: string
InferredUtcOffset:
type: object
nullable: true
description: UTC offset inferred from the candidate's own commit author timestamps, used when GitHub's `location` field is empty or unresolvable. Null when no signal is available.
properties:
offset:
type: number
example: -10
confidence:
type: string
enum:
- none
- low
- medium
- high
sample_size:
type: integer
description: Number of commits the inference was based on
share:
type: number
description: Fraction of sampled commits at the dominant offset
latest_commit:
type: string
format: date-time
Quota:
type: object
properties:
used:
type: integer
limit:
type: integer
description: -1 means unlimited
month:
type: string
example: 2026-07
cost:
type: integer
description: 'Credits this request debited (search: 1, get user: 1, evidence report: 3 when a report is returned and 0 while pending, analyze profile: 3, list operations: 0). `used` already reflects this charge.'
example: 1
rollover:
type: integer
description: Unused credits banked from the previous month and available on top of `limit` this month (`remaining` = limit + rollover - used). Only present on the Team plan, capped at one extra month's allowance (so at most 2x the allowance is usable in a month).
example: 340
OrgAffinity:
type: object
description: Present when `organizations` or `sectors` were given in the request.
properties:
score:
type: number
matches:
type: array
items:
type: object
properties:
org:
type: string
company:
type: string
sector:
type: string
TopRepositories:
type: array
maxItems: 3
description: The candidate's top repositories, as shown on the website candidate card
items:
type: object
properties:
name:
type: string
stars:
type: integer
language:
type: string
nullable: true
Evidence:
type: array
description: Where each matched skill/keyword was found on the candidate.
items:
type: object
properties:
keyword:
type: string
source:
type: string
enum:
- repo_name
- repo_description
- repo_language
- bio
- company
- skill_fallback
repo:
type: string
description: Bare repository name (no owner), present when the match came from a repository. Use `top_repositories` or the evidence report for owner-qualified names.
snippet:
type: string
description: Short surrounding text for bio/company matches
responses:
InternalError:
description: Unexpected server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Unauthorized:
description: Missing, invalid, or revoked API key
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
QuotaExceeded:
description: Monthly or daily quota exceeded
headers:
Retry-After:
schema:
type: integer
description: Seconds until the quota resets
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
BadRequest:
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'Authorization: Bearer ghk_live_...'
apiKeyAuth:
type: apiKey
in: header
name: X-Api-Key