Public API
Trackly API
Search 128K+ jobs across 1,900+ companies. Build something in 60 seconds.
Interactive playground: try any endpoint with your trk_ key.
Base URL
https://closeai.mba/api/v1Five public endpoints: /jobs, /jobs/:id, /companies, /companies/search, /me (alias: /stats).
Authentication
Every request needs an API key passed as a Bearer token in the Authorization header.
Authorization: Bearer trk_xxxxxxxxxxxxxxxxxxxxGenerate a key from the Developer section in Settings, then configure it in the CLI with trackly config --api-key trk_xxx or set TRACKLY_API_KEY=trk_xxx in your environment.
Quick Start
Three drop-in snippets that work on the first try. Replace trk_xxx with your own key.
Google Apps Script
Sync jobs into a Google Sheet. Dedupes by job ID, safe to re-run.
/**
* Trackly -> Google Sheets job sync.
* Copy into a Google Sheet (Extensions -> Apps Script) and run syncJobs().
*
* Columns: Job ID | Title | Company | Company Domain | Location | Function | URL | Posted At
*/
const TRACKLY_API_KEY = 'trk_xxxxxxxxxxxxxxxxxxxx'; // Settings -> Developer in Trackly.
const SHEET_NAME = 'Trackly Jobs';
// Real v1 query params. Adjust to taste.
const QUERY_PARAMS = {
jobFunction: 'finance,partnerships',
locationFilter: 'us',
jobModality: 'full_time',
limit: 50,
sort: 'newest',
};
function syncJobs() {
const url = 'https://closeai.mba/api/v1/jobs?' + Object
.entries(QUERY_PARAMS)
.map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v))
.join('&');
const response = UrlFetchApp.fetch(url, {
method: 'get',
headers: { Authorization: 'Bearer ' + TRACKLY_API_KEY },
muteHttpExceptions: true,
});
if (response.getResponseCode() !== 200) {
throw new Error('Trackly API ' + response.getResponseCode() + ': ' + response.getContentText());
}
const { jobs } = JSON.parse(response.getContentText());
const sheet = getOrCreateSheet_();
const lastRow = sheet.getLastRow();
const existingIds = lastRow > 1
? new Set(
sheet.getRange(2, 1, lastRow - 1, 1)
.getValues()
.flat()
.map(String)
)
: new Set();
const newRows = jobs
.filter((job) => !existingIds.has(String(job.id)))
.map((job) => [
job.id,
job.title,
job.companyName, // flat alias - do NOT use job.company (that's a nested object)
job.company.domain, // nested object access when you want domain
job.location,
job.jobFunction,
job.jobUrl, // NOT job.url
job.postedAt, // NOT job.posted_at
]);
if (newRows.length) {
sheet.getRange(sheet.getLastRow() + 1, 1, newRows.length, newRows[0].length).setValues(newRows);
}
}
function getOrCreateSheet_() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName(SHEET_NAME);
if (!sheet) {
sheet = ss.insertSheet(SHEET_NAME);
sheet.appendRow(['Job ID', 'Title', 'Company', 'Company Domain', 'Location', 'Function', 'URL', 'Posted At']);
sheet.setFrozenRows(1);
}
return sheet;
}Node.js
const response = await fetch('https://closeai.mba/api/v1/jobs?jobFunction=partnerships&limit=10', {
headers: { Authorization: `Bearer ${process.env.TRACKLY_API_KEY}` },
});
const { jobs, total } = await response.json();
for (const job of jobs) {
console.log(`${job.title} at ${job.companyName} - ${job.jobUrl}`);
console.log(` Company domain: ${job.company.domain}`);
}Python
import os
import requests
r = requests.get(
"https://closeai.mba/api/v1/jobs",
params={"jobFunction": "finance", "locationFilter": "us", "limit": 10},
headers={"Authorization": f"Bearer {os.environ['TRACKLY_API_KEY']}"},
)
r.raise_for_status()
for job in r.json()["jobs"]:
print(f'{job["title"]} at {job["companyName"]} - {job["jobUrl"]}')Rate Limits
All five public endpoints share a uniform limit of 10,000 requests per hour per user. Exceeding it returns a 429.
Each response includes X-RateLimit-Remaining and X-RateLimit-Reset headers.
Natural language search is experimental and not part of v1 - contact us if you need it.
Important semantics
Omitted params use YOUR saved preferences, not static defaults
locationFilter=us, jobModality=full_time, wantsPM=true. Pass every filter explicitly to make your script deterministic.job.company is a nested OBJECT, not a string
row.push(job.company) in a Google Sheet you'll see [object Object]. Use job.companyName for the flat string, or job.company.domain / job.company.fundingSeries to access nested fields./me (aliased as /stats) returns YOUR profile, not aggregate metrics
/me returns the authenticated user's profile, preferences, subscription, and feature flags. It is not a tracker-wide metrics endpoint. Both /api/v1/me and /api/v1/stats point at the same handler - prefer /me.Endpoints
/api/v1/jobsSearch and list job postings.
curl -H "Authorization: Bearer trk_xxx" \
"https://closeai.mba/api/v1/jobs?jobFunction=finance,partnerships&locationFilter=us&limit=2"Query parameters
| Param | Type | Notes |
|---|---|---|
jobFunction | string (comma-sep enum) | One or more of 14 canonical values: product, engineering, design, data, marketing, sales, partnerships, finance, strategy, operations, people, legal, support, other. Example: jobFunction=finance,partnerships. |
locationFilter | string (two modes) | Single-value: us | non_us | all | <region tag>. Comma-list: ONLY region tags (us/non_us/all are silently dropped if placed in a list). Region tags: europe, latam, middle_east, asia, africa, canada, oceania, remote, unknown. |
jobModality | full_time | internship | all | Employment type, NOT remote/hybrid/onsite. Filters the is_internship column. For remote, use usStates=REMOTE or region=remote. |
search | string | Keyword text. Matches against title, company name, and description. (NOT "keywords".) |
status | enum | Filters by the authenticated user's application pipeline state: new | applying | applied_confirmed | check_later | not_interested | all. Not a job-posting status. |
sort | newest | oldest | company | Default: newest. |
limit | integer | Default 20, max 50. |
offset | integer | Pagination offset. |
region | string (comma-sep region tags) | Comma-separated region tags. Takes priority over locationFilter when both are set. Example: region=us,canada. |
usStates | string (comma-sep) | Two-letter US state codes, optionally including the literal REMOTE token. Example: usStates=NY,CA,REMOTE. This is the primary way to fetch remote jobs. |
caProvinces | string (comma-sep) | Two-letter Canadian province codes, optionally including REMOTE. Example: caProvinces=ON,BC,REMOTE. |
internshipLevels | string (comma-sep) | undergraduate | graduate | mba | not_specified. Only applied when jobModality=internship. |
companyId | integer | Filter to a single company by ID. |
favoriteCompaniesOnly | boolean | Limits results to the caller's favorited companies. |
Response envelope
| Field | Type | Notes |
|---|---|---|
success | boolean | Always true on 200 responses. |
jobs | Job[] | Array of job objects (see schema below). |
total | number | Total jobs matching the filter (across all pages). |
hasMore | boolean | True if more jobs exist past this page. |
autoApplyDevEnabled | boolean | User-level auto-apply dev flag. |
servedFromCache | boolean | True if served from server-side cache. |
dataFreshnessMs | number | Cache age in ms. 0 = fresh from DB. |
pagination | object | { total, limit, offset, hasMore }. |
Job object (top-level fields)
| Field | Type | Notes |
|---|---|---|
id | number | Trackly job ID. |
externalId | string | ATS-native job ID. |
title | string | Job title. |
location | string | Free-form location string from the ATS. |
jobUrl | string (URL) | Apply URL. NOT "url". |
jobFunction | string | One of the 14 canonical function values. |
postedAt | string (ISO 8601) | When the company posted the job. |
firstSeenAt | string (ISO 8601) | When Trackly first scraped the job. |
isActive | boolean | True if the job is still live. |
isNew | boolean | True if posted within the last 24 hours. |
companyName | string | Convenience alias of company.name. Use this for flat spreadsheet output. |
companyId | number | Trackly company ID. |
companyDomain | string | Alias of company.domain. |
companyLogoUrl | string (URL) | Primary logo URL. |
companyLogoUrls | string[] | Ordered fallback logo URLs. |
company | object | Nested company object — NOT a string. See Company sub-table. |
atsSource | string | ATS vendor: ashby, greenhouse, lever, workday, etc. |
autoApplySupported | boolean | True if Trackly supports auto-apply on this ATS. |
isPM | boolean | Flagged as a product management role. |
isStrategyOps | boolean | Flagged as a strategy/operations role. |
isPartnership | boolean | Flagged as a partnerships role. SINGULAR in the response; the query-param is wantsPartnerships (plural, legacy). |
sponsorshipStatus | string | sponsored | unsponsored | unknown. |
sponsorshipEvidence | string | null | Quote or summary supporting the classification. |
sponsorshipClassifier | string | llm | reused | rule — how the classification was made. |
region | string | Single region tag: us | non_us | europe | canada | remote | ... |
seniorityLevel | string | null | junior | mid | senior | staff | director. |
salaryRange | string | null | Free-form range from the ATS when detected. |
visaSponsorship | string | null | Raw sponsorship note from the JD when detected. |
userStatus | string | null | The authenticated user's pipeline state for this job (new, applying, applied_confirmed, ...). Null if unset. |
applyStartedAt | string | null | When the user started applying. |
appliedConfirmedAt | string | null | When the user confirmed the application. |
statusUpdatedAt | string | null | Last time the user changed status. |
timeToApplyMinutes | number | null | User-measured apply duration. |
matchScore | number | null | Per-user match score (0–100). |
matchReasons | string[] | Per-user match reason labels. |
matchLabel | string | null | Per-user match tier label. |
matchConfidence | string | null | Per-user confidence label. |
matchConfidenceReasons | string[] | Per-user confidence reasons. |
missingSkills | string[] | Per-user: skills the user's resume lacks. |
dimensionScores | object[] | Per-user: 7-dimension score breakdown. |
matchExplanation | string | null | Per-user: natural language match explanation. |
job.company (nested object)
| Field | Type | Notes |
|---|---|---|
id | number | Trackly company ID. |
name | string | Company name. |
domain | string | Primary domain (e.g. stripe.com). |
logoUrl | string (URL) | Primary logo URL. |
logoUrls | string[] | Ordered fallback logo URLs. |
companyStage | string | null | seed | early | growth | unicorn | decacorn | enterprise. |
fundingStage | string | null | PitchBook funding stage label. |
fundingSeries | string | null | Most recent round (e.g. Series C). |
valuationMillions | number | null | Last known valuation in USD millions. |
lastFinancingDate | string | null | ISO 8601 date of last round. |
description | string | null | One-paragraph company description. |
ycBatch | string | null | YC batch code (e.g. S09) if applicable. |
Example response
{
"success": true,
"jobs": [
{
"id": 211202955,
"externalId": "c019b11f-4fbb-46a2-bbad-338ba8f4bedb",
"title": "Agentic Finance Engineer",
"companyName": "Mercor",
"companyId": 1059,
"companyDomain": "mercor.com",
"companyLogoUrl": "https://icon.horse/icon/mercor.com",
"location": "San Francisco",
"jobUrl": "https://jobs.ashbyhq.com/mercor/c019b11f-4fbb-46a2-bbad-338ba8f4bedb",
"atsSource": "ashby",
"autoApplySupported": false,
"isPM": false,
"isStrategyOps": false,
"isPartnership": false,
"jobFunction": "finance",
"postedAt": "2026-04-18T20:39:30.721Z",
"firstSeenAt": "2026-04-18T21:27:41.348Z",
"isNew": false,
"isActive": true,
"sponsorshipStatus": "unknown",
"region": "us",
"seniorityLevel": "mid",
"company": {
"id": 1059,
"name": "Mercor",
"domain": "mercor.com",
"logoUrl": "https://icon.horse/icon/mercor.com",
"companyStage": "decacorn",
"fundingStage": "Later Stage VC",
"fundingSeries": "Series C",
"valuationMillions": 10000,
"lastFinancingDate": "2026-02-04T00:00:00.000Z",
"description": "Developer of an artificial intelligence-powered hiring platform...",
"ycBatch": null
}
}
],
"total": 3,
"hasMore": true,
"autoApplyDevEnabled": false,
"servedFromCache": false,
"dataFreshnessMs": 0,
"pagination": { "total": 3, "limit": 2, "offset": 0, "hasMore": true }
}/api/v1/jobs/:idFetch a single job with the full description and (when available) structured JD fields.
curl -H "Authorization: Bearer trk_xxx" \
"https://closeai.mba/api/v1/jobs/211202955"The response uses the same shape as the list endpoint, plus the additional top-level fields below. The structured fields are flat siblings of title - there is no structuredJd wrapper object.
Additional detail fields
| Field | Type | Notes |
|---|---|---|
description | string | Plain-text full job description. |
descriptionHtml | string | HTML-formatted job description. |
minutesAgo | number | Minutes since postedAt. |
recommendations | string[] | Per-user: suggested actions to improve fit. |
quickWins | string[] | Per-user: quick resume tweaks. |
candidateFit | string | null | Per-user: narrative fit summary. |
skillsOverlap | object | null | Per-user: resume-vs-JD skill comparison. |
seniorityLevel | string | null | Optional (only when structured_jd is populated). Flat field, NOT nested under structuredJd. |
salaryMin | number | null | Optional. Flat field. |
salaryMax | number | null | Optional. Flat field. |
salaryCurrency | string | null | Optional. Flat field (e.g. USD). |
salaryPeriod | string | null | Optional. Flat field (year, hour, etc). |
structuredSkills | string[] | Optional. Required skills extracted from JD. |
structuredPreferredSkills | string[] | Optional. Nice-to-have skills. |
structuredBenefits | string[] | Optional. Benefits mentioned in JD. |
structuredTools | string[] | Optional. Tools/frameworks mentioned. |
structuredSponsorshipInfo | string | null | Optional. Extracted sponsorship info. |
Example response
{
"success": true,
"job": {
"id": 211202955,
"externalId": "c019b11f-4fbb-46a2-bbad-338ba8f4bedb",
"title": "Agentic Finance Engineer",
"location": "San Francisco",
"jobUrl": "https://jobs.ashbyhq.com/mercor/c019b11f-4fbb-46a2-bbad-338ba8f4bedb",
"description": "About Mercor\n\nMercor is defining the future of work...",
"descriptionHtml": "<h2><strong>About Mercor</strong></h2>...",
"isPM": false,
"isStrategyOps": false,
"isPartnership": false,
"jobFunction": "finance",
"postedAt": "2026-04-18T20:39:30.721Z",
"firstSeenAt": "2026-04-18T21:27:41.348Z",
"minutesAgo": 686,
"region": "us",
"seniorityLevel": "mid",
"salaryMin": null,
"salaryMax": null,
"salaryCurrency": null,
"salaryPeriod": null,
"structuredSkills": ["finance domain knowledge", "SQL", "Python"],
"structuredPreferredSkills": ["deploying AI agents", "LLM-powered tools"],
"structuredBenefits": [],
"structuredTools": [],
"structuredSponsorshipInfo": null,
"company": {
"id": 1059,
"name": "Mercor",
"domain": "mercor.com",
"atsType": "ashby",
"companyStage": "decacorn",
"fundingSeries": "Series C",
"valuationMillions": 10000
}
}
}/api/v1/companiesList monitored companies with per-function job counts.
curl -H "Authorization: Bearer trk_xxx" \
"https://closeai.mba/api/v1/companies?limit=50"Always pass limit - the response is large
functionCounts object plus funding metadata. A default-limit response with ~1,900 companies can be ~1 MB. Pass an explicit limit to keep payloads snappy.Query parameters
| Param | Type | Notes |
|---|---|---|
limit | integer | Default 20, max 50. |
offset | integer | Pagination offset. |
Response envelope
| Field | Type | Notes |
|---|---|---|
success | boolean | Always true on 2xx. |
companies | Company[] | See the Company object table below. |
count | number | Number of companies in this page. |
total | number | Total companies matching the filter (across all pages). |
servedFromCache | boolean | True if served from cache. |
dataFreshnessMs | number | Cache age in ms. 0 when fresh. |
Company object
| Field | Type | Notes |
|---|---|---|
id | number | Company ID. |
name | string | Company name. |
domain | string | Primary domain. |
logoUrl | string (URL) | Primary logo URL. |
atsType | string | ATS vendor identifier. |
industry | string | null | Classified industry tag. |
isFavorite | boolean | True if favorited by the authenticated user. |
pmJobCount | number | Active PM jobs at this company. |
totalJobCount | number | All active jobs. |
mbaJobCount | number | Active MBA-relevant jobs. |
functionCounts | object | { [function: string]: number } — 14-key breakdown across all canonical job functions. Large object. |
companyStage | string | null | seed | early | growth | unicorn | decacorn | enterprise. |
fundingStage | string | null | PitchBook funding stage label. |
fundingSeries | string | null | Most recent round. |
valuationMillions | number | null | USD millions. |
lastFinancingDate | string | null | ISO 8601 date. |
description | string | null | Company description. |
ycBatch | string | null | YC batch code. |
Example response
{
"success": true,
"companies": [
{
"id": 1,
"name": "Stripe",
"domain": "stripe.com",
"logoUrl": "https://icon.horse/icon/stripe.com",
"atsType": "greenhouse",
"industry": "fintech",
"isFavorite": false,
"pmJobCount": 13,
"totalJobCount": 227,
"mbaJobCount": 4,
"functionCounts": {
"product": 13, "engineering": 64, "design": 7, "data": 7,
"marketing": 16, "sales": 32, "partnerships": 4, "finance": 11,
"strategy": 10, "operations": 22, "people": 9, "legal": 3,
"support": 3, "other": 26
},
"companyStage": "enterprise",
"ycBatch": "S09"
}
],
"count": 1965,
"servedFromCache": false,
"dataFreshnessMs": 0
}/api/v1/companies/searchSemantic company search by name, domain, or description.
curl -H "Authorization: Bearer trk_xxx" \
"https://closeai.mba/api/v1/companies/search?q=stripe"Query parameters
| Param | Type | Notes |
|---|---|---|
q | string | Search text. Required. Min 2 chars, max 200. |
limit | integer | Default 20, max 50. |
Response shape
Same companies[] array as /companies. Envelope fields:
| Field | Type | Notes |
|---|---|---|
success | boolean | Always true on 2xx. |
companies | Company[] | Same shape as /companies. |
count | number | Number of companies in this page. |
total | number | Total companies matching the search (across all pages). |
searchSource | string | sql_fallback | ... - indicates which search path served the query. |
servedFromCache | boolean | True if served from cache. |
dataFreshnessMs | number | Cache age in ms. 0 when fresh. |
Example response
{
"success": true,
"companies": [ /* same shape as /companies list */ ],
"count": 1,
"total": 1,
"searchSource": "sql_fallback",
"servedFromCache": false,
"dataFreshnessMs": 0
}/api/v1/mealias: /api/v1/statsThe authenticated user's profile, saved preferences, subscription, and feature flags.
curl -H "Authorization: Bearer trk_xxx" \
"https://closeai.mba/api/v1/me"user object
| Field | Type | Notes |
|---|---|---|
id | number | User ID. |
email | string | User email. |
name | string | Display name. |
picture | string (URL) | Avatar URL. |
autoApplyDevEnabled | boolean | Auto-apply dev flag. |
preferences | object | Saved job preferences (see sub-table). |
subscription | object | { status: string, expiresAt: string | null }. |
onboardingCompleted | boolean | True if onboarding finished. |
featureFlags | object | { networkingV1: boolean } (currently). |
user.preferences
| Field | Type | Notes |
|---|---|---|
wantsPM | boolean | User wants product management roles. |
wantsStrategyOps | boolean | User wants strategy/ops roles. |
wantsPartnerships | boolean | User wants partnerships roles. |
wantedJobFunctions | string[] | Up to 14 canonical function values the user wants to see. |
locationFilter | string | Saved location filter (same semantics as the query param). |
needsSponsorship | boolean | User needs visa sponsorship. |
jobModality | string | full_time | internship | all. |
experienceRange | any | null | Legacy experience-range object. |
experienceRanges | any | null | Newer multi-range object. |
internshipLevels | string[] | null | undergraduate/graduate/mba/not_specified. |
favoritesDailyDigest | boolean | Opt-in to daily email digest of favorited companies. |
notificationsFavoritesOnly | boolean | Limit notifications to favorited companies. |
notificationFrequency | string | realtime | daily | weekly | off. |
Example response
{
"success": true,
"user": {
"id": 1,
"email": "user@example.com",
"name": "Example User",
"picture": "https://example.com/avatar.png",
"autoApplyDevEnabled": false,
"preferences": {
"wantsPM": true,
"wantsStrategyOps": false,
"wantsPartnerships": false,
"wantedJobFunctions": ["product"],
"locationFilter": "us",
"needsSponsorship": false,
"jobModality": "full_time",
"experienceRange": null,
"experienceRanges": null,
"internshipLevels": null,
"favoritesDailyDigest": true,
"notificationsFavoritesOnly": false,
"notificationFrequency": "realtime"
},
"subscription": { "status": "none", "expiresAt": null },
"onboardingCompleted": true,
"featureFlags": { "networkingV1": false }
}
}Errors
Error responses use standard HTTP status codes and include a JSON body:
{
"error": "Invalid or revoked API key"
}| Code | Meaning |
|---|---|
| 400 | Invalid request - usually a bad enum value on status or jobFunction, or a q shorter than 2 chars on /companies/search. |
| 401 | Missing, invalid, or revoked API key. |
| 429 | Rate limit exceeded (10,000 requests/hour). |
| 500 | Internal server error. |