01 Quickstart
Integration flow
- Request API access and store the issued key server-side.
- Collect the user profile, preferences, and hard constraints in your product.
- Send the profile to
POST /v1/match. - Render the returned matches with employer, title, score, explanation, and source URL.
The API is designed to sit behind your application server. Do not expose API keys in browser JavaScript, mobile apps, or public repositories.
02 Access and auth
Base URL and headers
Production API access is currently issued to approved partners. Confirm your final base URL during onboarding; examples use the public production host.
The partner API and member browser API use different bearer credentials. Partner integrations use an issued Facultas API key. The member resume library uses a short-lived Google ID token for the signed-in person and never places a partner key in browser code.
- Base URL
https://api.facultas.work- Version
v1- Authentication
X-API-Key: FACULTAS_API_KEY- Content type
application/json
03 Endpoint
POST /v1/match
Submit a profile and receive ranked job or internship matches. Hard constraints are applied before ranking, so returned matches should be eligible candidates for display.
curl https://api.facultas.work/v1/match \
-H "X-API-Key: $FACULTAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profile_id": "candidate_123",
"background": {
"field_of_study": "International Affairs",
"degree_level": "masters",
"years_experience": 2,
"experience_summary": "Graduate student in international affairs with quantitative research experience.",
"languages": ["English", "Spanish"],
"work_authorized_countries": ["USA"]
},
"interests": ["climate policy", "public sector"],
"hard_skills": ["Python", "GIS", "policy analysis"],
"hard_constraints": {
"job_types": ["internship"],
"excluded_sectors": ["defense"],
"availability_start": "2026-06-01",
"needs_visa_sponsorship": false
},
"preferences": {
"preferred_locations": ["Washington", "Remote"],
"preferred_sectors": ["Public/Government"]
},
"options": {
"limit": 10,
"include_explanations": true
}
}'
04 Request schema
Request fields
Unknown fields are ignored, not rejected. A request using the
wrong shape returns 200 OK with results that silently disregard
what you sent. If matches look generic, check your payload against the field
names below first — in particular hard_skills and
interests are top level, and limit lives under
options.
| Field | Type | Required | Description |
|---|---|---|---|
profile_id |
string | Yes | Your stable identifier for the user or session. Opaque — never send names, emails, or other personal identifiers. |
background.field_of_study |
string | No | Primary academic or professional field. A ranking signal, never a filter. |
background.degree_level |
string | No | high school, associate, bachelors, masters, phd. |
background.years_experience |
number | No | Years of professional experience. Drives the experience-gap demotion — supply it if you have it. |
background.experience_summary |
string | No | Free text. Contributes heavily to semantic matching, so a richer summary meaningfully improves results. |
background.languages |
array<string> | No | Spoken languages, English names or ISO 639-1 codes. |
background.work_authorized_countries |
array<string> | No | Turns on the work-authorization gate when supplied. |
background.is_us_citizen |
boolean | No | Only used with options.require_eligible, and only to drop postings with a confirmed citizenship requirement. |
background.clearance_status |
string | No | active, inactive, none, or unknown. Supplying it activates clearance handling. |
interests |
array<string> | No | Sectors, issue areas, or mission preferences. Top level — not nested under background. |
hard_skills |
array<string> | No | Tools, methods, certifications. Top level. Ubiquitous office software is ignored — see Prioritizing constraints. |
hard_constraints.* |
object | No | Deal-breakers. Applied before ranking. See Prioritizing constraints. |
preferences.* |
object | No | Soft signals — preferred_locations, preferred_sectors, minimum_compensation. Influence rank, never exclude. |
options.limit |
integer | No | Matches to return. Default 20, max 100. Nested under options, not top level. |
options.include_explanations |
boolean | No | Default true. Returns a human-readable reason per match. |
options.exclude_seen |
boolean | No | Default true. Suppresses postings already served to this profile_id. |
05 Response shape
Ranked matches with explanations
Each match includes a normalized opportunity, canonical source URL, score, and explanation signals that your product can show directly or adapt for its own UI.
{
"request_id": "req_01hx7m8k9p",
"matches": [
{
"opportunity_id": "opp_wri_4271",
"title": "Climate Data Intern",
"employer": "World Resources Institute",
"location": "Washington, DC",
"employment_type": "internship",
"source_url": "https://example.org/jobs/climate-data-intern",
"score": 0.87,
"explanation": {
"summary": "Strong climate-policy fit with GIS and Python requirements.",
"criteria": [
{ "name": "skills", "score": 0.91, "evidence": ["Python", "GIS"] },
{ "name": "field_fit", "score": 0.86, "evidence": ["climate policy"] },
{ "name": "placement_prior", "score": 0.78, "evidence": ["6 prior placements"] }
]
}
}
],
"meta": {
"limit": 10,
"returned": 1
}
}
06 Ranking behavior
How Facultas decides what to return
Hard constraints first
Work authorization, start dates, location requirements, exclusions, and similar gates are applied before ranking.
Hybrid retrieval
Semantic retrieval captures meaning while keyword retrieval preserves exact signals such as tools, certifications, and agency names.
Placement prior
Historical outcomes help rank employers that have actually hired from the relevant population.
Traceable output
Returned opportunities include source URLs and explanation criteria so a user can inspect why each match appeared.
07 Prioritizing constraints
Deal-breakers, gates, and preferences
Facultas applies candidate requirements in three tiers. Choosing the right tier matters more than any other request decision: put something too high and you get an empty page, too low and it barely registers.
Tier 1 — Hard constraints (hard_constraints)
Deal-breakers. Applied in SQL before retrieval, so an excluded posting can never appear at any rank. Use these only for things that make a posting genuinely unusable.
| Field | Default | Effect when set |
|---|---|---|
job_types | unset | Keeps only these types (internship, fulltime, parttime, fellowship, contract, temporary). |
allowed_countries | unset | Keeps only these countries. Remote roles bypass this unless remote_ok is false. |
remote_ok | true | Set false to exclude remote roles entirely. |
excluded_sectors | unset | Drops these sectors. Postings with no recorded sector still pass. |
paid_only | false | Drops confirmed unpaid postings. Off by default — unpaid internships and fellowships stay servable unless you ask otherwise. Unknown pay is always kept. |
needs_visa_sponsorship | false | Drops postings that explicitly refuse sponsorship. |
availability_start / availability_end | unset | Requires the posting window to overlap yours. Open-ended postings pass. |
Tier 2 — Eligibility gates (options.require_*)
Opt-in gates evaluated after retrieval and before ranking. Each produces a reasoned
verdict — eligible, ineligible, unknown,
or not_applicable — returned per match in
eligibility_evaluation. Only a confirmed ineligible removes
a posting. Every one of these defaults to off.
| Option | Gates on | Turn it on when |
|---|---|---|
require_clearance_match | Security clearance level | Your users cannot obtain clearances. Otherwise cleared roles are demoted but still shown. |
require_work_authorization_match | Posting country vs authorized countries | Also switches on automatically when work_authorized_countries is supplied. |
require_eligible | Confirmed citizenship requirements | Your users are non-citizens. Only drops postings with a structured requirement. |
require_min_compensation | preferences.minimum_compensation | Pay below the floor is unusable. Unknown pay is kept. |
require_degree_match | Posting degree level | Rarely. It compares level only, never field of study. |
require_experience_match | Minimum years | Rarely. This is a hard drop. Experience is already demoted smoothly — see below. |
require_language_match | Posting languages | The role is unperformable without the language. |
Prefer demotion to exclusion. Experience is the clearest example:
a candidate with 2 years is short of roughly a third of postings, so
require_experience_match would delete a third of the pool. Leaving it
off keeps the automatic gap-based demotion, which grows as the gap widens —
a one- or two-year stretch is barely penalised, while a role wanting five more
years than the candidate has drops far down. Reach for a gate only when a
mismatched posting is useless rather than merely ambitious.
Tier 3 — Preferences (preferences)
Soft signals. They move a posting up or down, never remove it.
preferred_locations, preferred_sectors, and
minimum_compensation (which stays soft unless you set
require_min_compensation).
Location and sector currently score as matched or not matched rather than on a gradient, so they carry more weight than their nominal share suggests. Listing many preferred locations dilutes the signal; listing one or two concentrates it. If your results skew heavily toward a single employer or city, over-specified preferences are the first thing to check.
How unknown data is treated
Missing information is never read as a mismatch. A posting that does not state a
requirement produces unknown, and the default policy allows it through
— the reasoning being that our extraction gaps should not cost a candidate an
opportunity. The single exception is job_type, which blocks on unknown,
because a request for internships should not return roles of indeterminate type. You
can override this per constraint with
options.eligibility_unknown_policy, for example
{"security_clearance": "block"}.
Practical ordering
- Start with only genuine deal-breakers in
hard_constraints— usually justjob_types. - Supply
backgroundfacts (years_experience,languages,work_authorized_countries,clearance_status). Several gates activate on their own once the facts are present. - Express everything else as
preferencesand let ranking sort it out. - Add
require_*gates only after seeing results that are genuinely unusable rather than simply imperfect. - Inspect
score_breakdownandeligibility_evaluationon real responses to see which signals actually moved.
08 Member resume API
Independent resume variants for authenticated members
These endpoints back the Facultas member resume library. They accept
Authorization: Bearer GOOGLE_ID_TOKEN; the local development bypass
may use X-Dev-Email only when both the page and API are running directly
on loopback and the backend bypass flag is enabled.
| Endpoint | Purpose |
|---|---|
GET /v1/member/resumes | List the signed-in member's safe resume metadata. |
POST /v1/member/resumes | Upload one or more independent PDF or DOCX files. |
PATCH /v1/member/resumes/{public_id} | Rename, change archetype, set default, archive, or restore an owned resume. |
DELETE /v1/member/resumes/{public_id} | Safely delete an owned resume under the retention policy. |
GET /v1/member/matching-settings | Read account-level eligibility and search constraints. |
PUT /v1/member/matching-settings | Replace structured eligibility, hard_constraints, preferences, and options with explicit member choices. |
POST /v1/member/matches | Match with exactly one named resume or aggregate across all ready resumes. |
Batch upload
Send repeated files multipart fields and an optional
metadata JSON array in the same order. Each metadata item may contain
a label of up to 80 characters and a non-null archetype such as
general, policy, or data. The response reports each file
independently, so accepted files remain accepted when a sibling fails.
curl https://api.facultas.work/v1/member/resumes \
-H "Authorization: Bearer $GOOGLE_ID_TOKEN" \
-F "[email protected]" \
-F "[email protected]" \
-F 'metadata=[{"label":"Policy","archetype":"policy"},{"label":"Data","archetype":"data"}]'
List and upload responses expose only safe metadata: public id, label,
archetype, default state, parse status and safe parse_error, original filename, content
type, parser/schema versions, timestamps, and archive state. Storage
paths and internal database identifiers are never part of the browser contract.
Single and all-resume matching
{ "mode": "single", "resume_id": "7b55d3b7-...", "limit": 10 }
{ "mode": "all", "limit": 10 }
Single mode requires one owned, ready resume and never merges another variant.
All mode evaluates ready variants, deduplicates opportunities, and annotates each
job with best_resume and optional alternate_resume_fits. The
response also includes mode, request_id, match provenance,
normal match fields, and metadata.
Account settings and explicit request overrides are authoritative for visa, citizenship, work authorization, clearance, geography, paid-only, compensation, and job-type constraints. A resume contributes retrieval and ranking signals only; it cannot silently relax or replace those constraints. Per-search overrides may narrow account hard constraints, but cannot loosen them.
{
"eligibility": {
"is_us_citizen": true,
"citizenships": ["United States"],
"work_authorized_countries": ["United States"],
"needs_visa_sponsorship": false,
"clearance_status": "active",
"clearance_level": "Public Trust"
},
"hard_constraints": {
"job_types": ["Full-time"],
"allowed_countries": ["United States"],
"remote_ok": true,
"excluded_sectors": ["Defense"],
"needs_visa_sponsorship": false,
"paid_only": true,
"availability_start": "2026-08-01"
},
"preferences": {
"preferred_locations": ["Washington DC"],
"preferred_sectors": ["Climate"],
"minimum_compensation": 65000
},
"options": {}
}
minimum_compensation is annual USD. The current engine does not accept a currency or pay-period field.
09 Errors
Error format and status codes
{
"detail": [
{
"type": "int_parsing",
"loc": ["body", "options", "limit"],
"msg": "Input should be a valid integer",
"input": "ten"
}
]
}
| Status | Meaning | Client action |
|---|---|---|
422 |
Body failed validation. detail[].loc names the offending field path. |
Fix the field named in loc. Note that unknown fields do not 422 — they are ignored. |
401 |
Missing or invalid API key. | Check the bearer token. |
422 |
Valid JSON, but required profile fields are missing. | Prompt for the missing fields or retry with a complete profile. |
429 |
Rate limit exceeded. | Back off and retry after the returned limit window. |
500 |
Unexpected server error. | Retry with exponential backoff and include request_id in support requests. |
10 Agent guidance
How agents should use the API
- Use
/llms.txtfor a compact machine-readable summary, then use this page for full field definitions. - Send only profile data needed for matching. Avoid names, emails, resumes, transcripts, and private notes unless explicitly required by a partner integration.
- Treat
constraintsas hard filters, not ranking preferences. - Show
source_urlwhenever presenting an opportunity so users can verify the live posting. - Present
scoreandexplanation.summaryas matching evidence, not as a guarantee of hiring or eligibility. - When an API response contains no matches, ask the user which constraints to relax before retrying.