Unable to connect. You appear to be offline.

Public API

Trackly API

Search 128K+ jobs across 1,900+ companies. Build something in 60 seconds.

Open full API reference

Interactive playground: try any endpoint with your trk_ key.

Base URL

https://closeai.mba/api/v1

Five 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_xxxxxxxxxxxxxxxxxxxx

Generate 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.

Code.gs
/**
 * 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

fetch-jobs.mjs
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

fetch_jobs.py
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

When you omit a query param, the server loads the authenticated user's saved preferences from their Trackly account and applies those as the baseline. Two users with different saved preferences will see different results from the same bare request - even with the same API key scope. For a brand-new user with no saved preferences, the fallback is 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

If you try 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

GET/api/v1/jobs

Search and list job postings.

curl
curl -H "Authorization: Bearer trk_xxx" \
  "https://closeai.mba/api/v1/jobs?jobFunction=finance,partnerships&locationFilter=us&limit=2"

Query parameters

ParamTypeNotes
jobFunctionstring (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.
locationFilterstring (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.
jobModalityfull_time | internship | allEmployment type, NOT remote/hybrid/onsite. Filters the is_internship column. For remote, use usStates=REMOTE or region=remote.
searchstringKeyword text. Matches against title, company name, and description. (NOT "keywords".)
statusenumFilters by the authenticated user's application pipeline state: new | applying | applied_confirmed | check_later | not_interested | all. Not a job-posting status.
sortnewest | oldest | companyDefault: newest.
limitintegerDefault 20, max 50.
offsetintegerPagination offset.
regionstring (comma-sep region tags)Comma-separated region tags. Takes priority over locationFilter when both are set. Example: region=us,canada.
usStatesstring (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.
caProvincesstring (comma-sep)Two-letter Canadian province codes, optionally including REMOTE. Example: caProvinces=ON,BC,REMOTE.
internshipLevelsstring (comma-sep)undergraduate | graduate | mba | not_specified. Only applied when jobModality=internship.
companyIdintegerFilter to a single company by ID.
favoriteCompaniesOnlybooleanLimits results to the caller's favorited companies.

Response envelope

FieldTypeNotes
successbooleanAlways true on 200 responses.
jobsJob[]Array of job objects (see schema below).
totalnumberTotal jobs matching the filter (across all pages).
hasMorebooleanTrue if more jobs exist past this page.
autoApplyDevEnabledbooleanUser-level auto-apply dev flag.
servedFromCachebooleanTrue if served from server-side cache.
dataFreshnessMsnumberCache age in ms. 0 = fresh from DB.
paginationobject{ total, limit, offset, hasMore }.

Job object (top-level fields)

FieldTypeNotes
idnumberTrackly job ID.
externalIdstringATS-native job ID.
titlestringJob title.
locationstringFree-form location string from the ATS.
jobUrlstring (URL)Apply URL. NOT "url".
jobFunctionstringOne of the 14 canonical function values.
postedAtstring (ISO 8601)When the company posted the job.
firstSeenAtstring (ISO 8601)When Trackly first scraped the job.
isActivebooleanTrue if the job is still live.
isNewbooleanTrue if posted within the last 24 hours.
companyNamestringConvenience alias of company.name. Use this for flat spreadsheet output.
companyIdnumberTrackly company ID.
companyDomainstringAlias of company.domain.
companyLogoUrlstring (URL)Primary logo URL.
companyLogoUrlsstring[]Ordered fallback logo URLs.
companyobjectNested company object — NOT a string. See Company sub-table.
atsSourcestringATS vendor: ashby, greenhouse, lever, workday, etc.
autoApplySupportedbooleanTrue if Trackly supports auto-apply on this ATS.
isPMbooleanFlagged as a product management role.
isStrategyOpsbooleanFlagged as a strategy/operations role.
isPartnershipbooleanFlagged as a partnerships role. SINGULAR in the response; the query-param is wantsPartnerships (plural, legacy).
sponsorshipStatusstringsponsored | unsponsored | unknown.
sponsorshipEvidencestring | nullQuote or summary supporting the classification.
sponsorshipClassifierstringllm | reused | rule — how the classification was made.
regionstringSingle region tag: us | non_us | europe | canada | remote | ...
seniorityLevelstring | nulljunior | mid | senior | staff | director.
salaryRangestring | nullFree-form range from the ATS when detected.
visaSponsorshipstring | nullRaw sponsorship note from the JD when detected.
userStatusstring | nullThe authenticated user's pipeline state for this job (new, applying, applied_confirmed, ...). Null if unset.
applyStartedAtstring | nullWhen the user started applying.
appliedConfirmedAtstring | nullWhen the user confirmed the application.
statusUpdatedAtstring | nullLast time the user changed status.
timeToApplyMinutesnumber | nullUser-measured apply duration.
matchScorenumber | nullPer-user match score (0–100).
matchReasonsstring[]Per-user match reason labels.
matchLabelstring | nullPer-user match tier label.
matchConfidencestring | nullPer-user confidence label.
matchConfidenceReasonsstring[]Per-user confidence reasons.
missingSkillsstring[]Per-user: skills the user's resume lacks.
dimensionScoresobject[]Per-user: 7-dimension score breakdown.
matchExplanationstring | nullPer-user: natural language match explanation.

job.company (nested object)

FieldTypeNotes
idnumberTrackly company ID.
namestringCompany name.
domainstringPrimary domain (e.g. stripe.com).
logoUrlstring (URL)Primary logo URL.
logoUrlsstring[]Ordered fallback logo URLs.
companyStagestring | nullseed | early | growth | unicorn | decacorn | enterprise.
fundingStagestring | nullPitchBook funding stage label.
fundingSeriesstring | nullMost recent round (e.g. Series C).
valuationMillionsnumber | nullLast known valuation in USD millions.
lastFinancingDatestring | nullISO 8601 date of last round.
descriptionstring | nullOne-paragraph company description.
ycBatchstring | nullYC batch code (e.g. S09) if applicable.

Example response

200 OK
{
  "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 }
}
GET/api/v1/jobs/:id

Fetch a single job with the full description and (when available) structured JD fields.

curl
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

FieldTypeNotes
descriptionstringPlain-text full job description.
descriptionHtmlstringHTML-formatted job description.
minutesAgonumberMinutes since postedAt.
recommendationsstring[]Per-user: suggested actions to improve fit.
quickWinsstring[]Per-user: quick resume tweaks.
candidateFitstring | nullPer-user: narrative fit summary.
skillsOverlapobject | nullPer-user: resume-vs-JD skill comparison.
seniorityLevelstring | nullOptional (only when structured_jd is populated). Flat field, NOT nested under structuredJd.
salaryMinnumber | nullOptional. Flat field.
salaryMaxnumber | nullOptional. Flat field.
salaryCurrencystring | nullOptional. Flat field (e.g. USD).
salaryPeriodstring | nullOptional. Flat field (year, hour, etc).
structuredSkillsstring[]Optional. Required skills extracted from JD.
structuredPreferredSkillsstring[]Optional. Nice-to-have skills.
structuredBenefitsstring[]Optional. Benefits mentioned in JD.
structuredToolsstring[]Optional. Tools/frameworks mentioned.
structuredSponsorshipInfostring | nullOptional. Extracted sponsorship info.

Example response

200 OK
{
  "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
    }
  }
}
GET/api/v1/companies

List monitored companies with per-function job counts.

curl
curl -H "Authorization: Bearer trk_xxx" \
  "https://closeai.mba/api/v1/companies?limit=50"

Always pass limit - the response is large

Each company includes a 14-key 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

ParamTypeNotes
limitintegerDefault 20, max 50.
offsetintegerPagination offset.

Response envelope

FieldTypeNotes
successbooleanAlways true on 2xx.
companiesCompany[]See the Company object table below.
countnumberNumber of companies in this page.
totalnumberTotal companies matching the filter (across all pages).
servedFromCachebooleanTrue if served from cache.
dataFreshnessMsnumberCache age in ms. 0 when fresh.

Company object

FieldTypeNotes
idnumberCompany ID.
namestringCompany name.
domainstringPrimary domain.
logoUrlstring (URL)Primary logo URL.
atsTypestringATS vendor identifier.
industrystring | nullClassified industry tag.
isFavoritebooleanTrue if favorited by the authenticated user.
pmJobCountnumberActive PM jobs at this company.
totalJobCountnumberAll active jobs.
mbaJobCountnumberActive MBA-relevant jobs.
functionCountsobject{ [function: string]: number } — 14-key breakdown across all canonical job functions. Large object.
companyStagestring | nullseed | early | growth | unicorn | decacorn | enterprise.
fundingStagestring | nullPitchBook funding stage label.
fundingSeriesstring | nullMost recent round.
valuationMillionsnumber | nullUSD millions.
lastFinancingDatestring | nullISO 8601 date.
descriptionstring | nullCompany description.
ycBatchstring | nullYC batch code.

Example response

200 OK
{
  "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
}
GET/api/v1/mealias: /api/v1/stats

The authenticated user's profile, saved preferences, subscription, and feature flags.

curl
curl -H "Authorization: Bearer trk_xxx" \
  "https://closeai.mba/api/v1/me"

user object

FieldTypeNotes
idnumberUser ID.
emailstringUser email.
namestringDisplay name.
picturestring (URL)Avatar URL.
autoApplyDevEnabledbooleanAuto-apply dev flag.
preferencesobjectSaved job preferences (see sub-table).
subscriptionobject{ status: string, expiresAt: string | null }.
onboardingCompletedbooleanTrue if onboarding finished.
featureFlagsobject{ networkingV1: boolean } (currently).

user.preferences

FieldTypeNotes
wantsPMbooleanUser wants product management roles.
wantsStrategyOpsbooleanUser wants strategy/ops roles.
wantsPartnershipsbooleanUser wants partnerships roles.
wantedJobFunctionsstring[]Up to 14 canonical function values the user wants to see.
locationFilterstringSaved location filter (same semantics as the query param).
needsSponsorshipbooleanUser needs visa sponsorship.
jobModalitystringfull_time | internship | all.
experienceRangeany | nullLegacy experience-range object.
experienceRangesany | nullNewer multi-range object.
internshipLevelsstring[] | nullundergraduate/graduate/mba/not_specified.
favoritesDailyDigestbooleanOpt-in to daily email digest of favorited companies.
notificationsFavoritesOnlybooleanLimit notifications to favorited companies.
notificationFrequencystringrealtime | daily | weekly | off.

Example response

200 OK
{
  "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"
}
CodeMeaning
400Invalid request - usually a bad enum value on status or jobFunction, or a q shorter than 2 chars on /companies/search.
401Missing, invalid, or revoked API key.
429Rate limit exceeded (10,000 requests/hour).
500Internal server error.