ARes Partner API

Documentation

Introduction

The Partner API gives approved organisations read access to open job listings, the companies behind them and aggregated market statistics. It is a JSON REST API over HTTPS.

Base URL: https://aresume.ai/api/v1. A machine-readable description is available at https://aresume.ai/api/v1/openapi.json (OpenAPI 3.1), which you can import into Postman, Insomnia or a client generator.

Authentication

Create a key in the Partnership Portal and send it with every request:

Authorization: Bearer ares_live_...

The X-API-Key header is also accepted. Keys in the URL are not, because URLs end up in logs. Keep keys on your servers: the API does not send CORS headers, so browser code cannot call it directly.

Each key has scopes:

ScopeAllows
jobs:readSearch and read job listings
companies:readSearch and read companies
stats:readRead market and salary statistics

You can also restrict a key to specific IP addresses or ranges. Revoke a key at any time; it stops working immediately.

Requests and responses

  • All endpoints use GET and return JSON encoded in UTF-8.
  • Times are UTC in ISO 8601 (2026-09-29T08:00:00Z); dates are YYYY-MM-DD.
  • Every response carries an X-Request-Id. Include it when you contact us about a request.
  • Metered responses also carry X-ARes-Units, X-ARes-Cost and X-ARes-Balance.
  • The version is part of the path. Fields may be added to v1 without notice, so ignore fields you do not know; nothing is removed or renamed within v1.

Example

curl "https://aresume.ai/api/v1/jobs?q=data+engineer&country=BE&limit=2" \
  -H "Authorization: Bearer $ARES_API_KEY"
import os, requests

response = requests.get(
    "https://aresume.ai/api/v1/jobs",
    params={"q": "data engineer", "country": "BE", "limit": 2},
    headers={"Authorization": f"Bearer {os.environ['ARES_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
for job in response.json()["data"]:
    print(job["title"], job["company"]["name"] if job["company"] else "")
// Node.js 18 or later, on your server
const response = await fetch("https://aresume.ai/api/v1/jobs?country=NL&limit=2", {
  headers: { Authorization: `Bearer ${process.env.ARES_API_KEY}` },
});
if (!response.ok) throw new Error((await response.json()).error.message);
const { data, pagination } = await response.json();

Sample response

{
  "data": [
    {
      "id": 201512,
      "title": "Data Engineer",
      "company": {"id": 88, "name": "Example Bank"},
      "location": {"city": "Ghent", "region": "East Flanders", "country": "Belgium",
                   "country_code": "BE", "display": "Ghent, Belgium"},
      "remote_type": "hybrid",
      "employment_type": "full-time",
      "experience_level": "mid",
      "category": "Data Science",
      "salary": null,
      "skills": ["python", "sql", "airflow"],
      "summary": "Build and run the data platform behind our retail products...",
      "posted_date": "2026-09-28",
      "updated_at": "2026-09-29T06:12:44Z",
      "apply_url": "https://careers.example.com/jobs/1234",
      "url": "https://aresume.ai/jobs/201512",
      "source": "company_careers",
      "urgently_hiring": false
    }
  ],
  "pagination": {"limit": 2, "has_more": true, "next_cursor": "eyJpIjoy..."}
}

Pagination and sync

List endpoints return up to limit results (default 20, maximum set by your plan). When pagination.has_more is true, pass pagination.next_cursor as cursor with the same filters to get the next page. Cursors are signed and only valid for the sort order that produced them.

To keep a local copy current, request sort=updated_asc with updated_after set to the time of your last sync, and page until has_more is false. updated_at only moves when a field you receive changes or a closed job reopens; routine re-checks of the source do not move it, so each sync only returns (and charges for) jobs that actually changed. A listing that stops appearing in GET /jobs has closed.

Rate limits

Limits apply per organisation, across all your keys:

PlanPer minutePer dayPage size
Sandbox1010020
Standard6020,00050
Scale300200,000100

Responses include X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix time) for the current minute, plus X-RateLimit-Daily-Limit and X-RateLimit-Daily-Remaining. The daily count resets at 00:00 UTC. Over a limit, the API answers 429 with a Retry-After header in seconds; wait that long before retrying.

Pricing and billing

Each endpoint costs a number of units (listed with each endpoint below). Lookups and statistics cost a fixed number of units per call. Searches cost their units once per started block of 10 results returned, so a page of 1 to 10 results costs 1 unit and a page of 50 costs 5; an empty page costs 1. Your plan sets the price per unit and how many units are free each calendar month (UTC). Only successful responses (2xx) are charged; errors and throttled calls are free. Every response reports its cost in the X-ARes-Units and X-ARes-Cost headers.

PlanPrice per unit (USD)Free units per monthAvailability
Sandbox Free 250 Open
Standard $0.018 0 Coming soon
Scale $0.012 0 Coming soon

On a free plan the free units are also the monthly limit; when they run out the API answers 402 with free_quota_exhausted until the 1st of the next month (UTC). Paid plans open later; they will draw from a prepaid balance that you top up by card in the portal. GET /account/usage returns your balance and usage and is free.

Errors

Errors return a JSON body:

{"error": {"code": "invalid_parameter", "message": "limit must be between 1 and 50 on your plan.",
           "param": "limit", "request_id": "9f1c..."}}
StatusCodeMeaning
400invalid_parameterA parameter is missing or invalid; see param.
401missing_api_key, invalid_api_keyNo key was sent, or the key is unknown.
402insufficient_balance, free_quota_exhaustedAdd credit, or move to a paid plan.
403key_revoked, scope_missing, ip_not_allowed, terms_not_accepted, account_suspended, no_planThe key or account is not allowed to make this call.
404not_foundThe resource or endpoint does not exist.
429rate_limited, daily_quota_exceededWait for Retry-After seconds.
500internal_errorOur fault. Retry with backoff and quote the request ID if it persists.

Endpoints

GET/api/v1/jobs/{job_id} 1 unit · jobs:read

Get one job

A single open job with its full plain-text description.

ParameterTypeDescription
job_id required integer (path) Job ID.
GET/api/v1/companies/{company_id} 1 unit · companies:read

Get one company

A company with its open-job count and the countries and categories it hires in.

ParameterTypeDescription
company_id required integer (path) Company ID.
GET/api/v1/stats/markets 3 units · stats:read

Open jobs by market

Counts of open jobs grouped by country, category, work type, contract type or source, with the number posted in the last 7 days.

ParameterTypeDescription
group_by string Grouping. Default country. One of: country, category, remote, employment_type, source.
country string Limit to one country (name or ISO code).
q string Words that must all appear in the job title.
GET/api/v1/stats/salaries 3 units · stats:read

Salary distribution

Yearly salary percentiles for open jobs that state a salary, per currency. Monthly, weekly, daily and hourly figures are converted to yearly.

ParameterTypeDescription
q string Words that must all appear in the job title. Example: product manager
country string Country name or ISO code. Example: NL
category string Category name.
GET/api/v1/account/usage Free

Your usage and balance

Your plan, balance, remaining free units and the last 30 days of usage. Free of charge.

Data sources and attribution

The API currently includes listings from: Posted on ARes, Company careers feeds. Each listing's source field tells you where it came from.

When you show a listing, name ARes as the source and link to its url (the listing on ARes) or its apply_url. See the API terms for how long you may keep data.

Salary statistics treat salaries without a stated period as yearly, unless the amount is clearly hourly (under 300) or monthly (under 15,000) in USD, EUR, GBP, CHF, CAD, AUD, NZD or SGD.

Changelog

  • 2026-09-30: v1 released.