REST API · v2

Vocations
API Docs

Integrate job-offer monitoring and company intelligence directly into your recruiting workflow.

Introduction

Authentication

All endpoints under /v2 require a valid API key, including those that do not consume usage.

Endpoints under /playground do not require an API key.

For /v2 endpoints, the key must be passed in the Authorization HTTP header with value Bearer <key>.

http header
Authorization: Bearer 5tg••••••••••••••••8ux
Introduction

Shared types

ℹ️

? after any key in a type means the value is optional.
[] after any type means the value, if provided, must be an array.
All ids have a length of 16 characters.
Search endpoints accept page (zero-based, default 0) and pageSize (minimum 1, maximum 128, default 10).
Filter arrays accept up to 64 items, except names, cities, provinces, regions, nations, postalCodes, italy.ivas, italy.ateco, spain.cifNifs and spain.cnae, which accept up to 256. Batch endpoints accept between 1 and 128 IDs. Exceeding a limit returns 400.
Every authenticated response returns X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset for the current minute window. A 429 also returns Retry-After.

shared types
type ErrorResponse = { message: string };

type CountryKey = "italy" | "spain";

type LocationLevel = {
  level: 4 | 6 | 8;
  value: string;
};

type LocationFilter = {
  country: CountryKey;
  levels: LocationLevel[];
};

type Location = {
  city?: string;
  province?: string;
  region?: string;
  nation?: string;
  latitude?: number;
  longitude?: number;
};

type PendingStatus =
  | "idle"
  | "pending"
  | "completed"
  | "notAvailable"
  | "failed";

type NumericRange =
  | { min: number; max: number }
  | { min: number; max?: undefined }
  | { min?: undefined; max: number };

type YearlySalaryRange = {
  min?: number;
  max?: number;
  currency: string;
};

type YearlyValue = {
  year: number;
  value: number;
};

type SortType =
  | "default"
  | "name"
  | "city"
  | "province"
  | "region"
  | "sectors"
  | "currentTotalRevenue"
  | "currentEmployees"
  | "currentOpenPositions";

type SortOrder = "asc" | "desc";

type PositionSource = "vocations" | "linkedIn" | "indeed" | "infoJobs";

type Hours = "full" | "part" | "shift";

type CompanyStatus = "open" | "bankruptcy" | "liquidation";

type ValueTrend = "increasing" | "decreasing" | "stable";

type ExperienceLevel = "0" | "0-1" | "1-3" | "3-5" | "5-10" | "10+";

type Contract =
  | "temporary"
  | "internship"
  | "permanent"
  | "contract"
  | "apprenticeship"
  | "freelance";

type Collar = "blue" | "white";

type SmartWorking = "yes" | "no" | "hybrid";

type BusinessUnit =
  | "finance"
  | "hr"
  | "purchasing"
  | "marketing"
  | "sales"
  | "customerService"
  | "research"
  | "operations"
  | "it"
  | "legal"
  | "strategy"
  | "sustainability"
  | "administration";

type SectorKey =
  | "administrationAccounting"
  | "textileClothing"
  | "mediaCommunicationMarketing"
  | "aeronauticsNavalDefense"
  | "realEstate"
  | "agriculture"
  | "horeca"
  | "food"
  | "artHistoryArchitecture"
  | "furniture"
  | "craftsmanship"
  | "insurance"
  | "socialCooperativesAssociations"
  | "automotive"
  | "banking"
  | "biomedical"
  | "broadcastingMusicFilm"
  | "outsourcingBusinessServices"
  | "retail"
  | "manufacturingIndustrial"
  | "chemical"
  | "energyOilGas"
  | "trade"
  | "consultingITConsulting"
  | "consumerGoods"
  | "pharmaceutical"
  | "environmentSustainabilityEcology"
  | "publishing"
  | "educationTraining"
  | "electronicsElectrotechnics"
  | "industrialEngineeringServices"
  | "sportsCultureEntertainment"
  | "publicEntitiesNonProfitPublicAdmin"
  | "financeFinancialServices"
  | "gamingBetting"
  | "rubberPlastic"
  | "largeScaleRetail"
  | "healthHygiene"
  | "humanResources"
  | "warehousePackaging"
  | "cybersecurityIT"
  | "legalCompliance"
  | "logistics"
  | "transportShipping"
  | "medical"
  | "metallurgyMechanics"
  | "exhibitionsFairsEvents"
  | "motors"
  | "cleaning"
  | "securitySurveillance"
  | "personalServices"
  | "professionalFirm"
  | "telecommunications"
  | "tourismTravel"
  | "designFashion"
  | "multiservices"
  | "technologyScienceData"
  | "automation"
  | "other";
  
  type ItalyLegalForm =
  | "AA"
  | "AC"
  | "AE"
  | "AF"
  | "AI"
  | "AL"
  | "AM"
  | "AN"
  | "AP"
  | "AR"
  | "AS"
  | "AT"
  | "AU"
  | "AZ"
  | "CC"
  | "CE"
  | "CF"
  | "CI"
  | "CL"
  | "CM"
  | "CN"
  | "CO"
  | "CR"
  | "CS"
  | "CZ"
  | "DI"
  | "EC"
  | "ED"
  | "EE"
  | "EI"
  | "EL"
  | "EM"
  | "EN"
  | "EP"
  | "ER"
  | "ES"
  | "FI"
  | "FO"
  | "GE"
  | "IC"
  | "ID"
  | "IF"
  | "IR"
  | "LL"
  | "MA"
  | "OC"
  | "OO"
  | "OS"
  | "PA"
  | "PC"
  | "PF"
  | "PS"
  | "RC"
  | "RR"
  | "RS"
  | "SA"
  | "SC"
  | "SD"
  | "SE"
  | "SF"
  | "SG"
  | "SI"
  | "SL"
  | "SM"
  | "SN"
  | "SO"
  | "SP"
  | "SR"
  | "SS"
  | "ST"
  | "SU"
  | "SV"
  | "SZ";

type SpainLegalForm =
  | "GE"
  | "IE"
  | "IU"
  | "PO"
  | "AC"
  | "CJ"
  | "CB"
  | "CD"
  | "BI"
  | "PH"
  | "CG"
  | "CL"
  | "EX"
  | "PV"
  | "NL"
  | "NR"
  | "PJ"
  | "FN"
  | "FJ"
  | "FD"
  | "MT"
  | "MU"
  | "OA"
  | "PP"
  | "AG"
  | "SM"
  | "SA"
  | "ES"
  | "SD"
  | "SE"
  | "SB"
  | "SF"
  | "SU"
  | "CV"
  | "CP"
  | "CO"
  | "CA"
  | "CS"
  | "SC"
  | "SG"
  | "GR"
  | "LD"
  | "SL"
  | "LL"
  | "LP"
  | "NE"
  | "LF"
  | "LU"
  | "UC"
  | "UT"
  | "OR";
Introduction

Playground

Use the playground to explore endpoints without an API key. The playground base path is https://vocations.io/api/playground, it mirrors the v2 endpoints exactly and runs on synthetic Italian and Spanish data.

API Playground — vocations.io
No API Key required
Companies
Positions
Contacts
Usage
POSTvocations.io/api/playground/companies
curl -X POST https://vocations.io/api/playground/companies \
  -H "Content-Type: application/json" \
  -d '{
    "page": 0,
    "company": {
      "locations": [
        {
          "country": "spain",
          "levels": [{ "level": 4, "value": "Cataluña" }]
        }
      ],
      "sectors": ["technologyScienceData"],
      "employees": [{ "min": 11, "max": 200 }]
    },
    "sortType": "name",
    "sortOrder": "asc",
    "withPositions": true
  }'
🔒 No key needed in playground
Response200 OK
{
  "companyIDs": [
    "pc0000000000000f",
    "pc0000000000001j",
    "pc0000000000002b"
  ],
  "totalPages": 22,
  "totalResults": 217
}
This is a visual preview. To interact with the real playground go to vocations.io/api/playground → vocations.io/api/playground →
1.2 Company Enrichment

POST /companies/enrich

💳 1 credit/company · 2 credits with financial data

Performs company enrich using provided IDs.

Request

request type
type V2CompanyEnrichRequest = {
  companyIDs: string[];
};

Response 200

response
type V2EnrichedCompany = {
  companyID: string;
  parentHolding?: string;
  name: string;

  workAgency: boolean;
  openPositionCount: number;

  url?: string;
  imageFile?: string;
  linkedInURL?: string;
  indeedURL?: string;
  infoJobsURL?: string;

  address?: string;
  location?: LocationLevel[];
  nation?: string;

  sectors: SectorKey[];

  employees: YearlyValue[];

  totalRevenue: YearlyValue[];
  employeesCost: YearlyValue[];
  netProfit: YearlyValue[];

  italy?: {
    ragioneSociale: string;
    iva?: string;
    ateco?: string;
    atecoCode?: string;
    rea?: string;
    shareCapital?: number;
    legalForm?: ItalyLegalForm;
    chamberOfCommerce?: string;
  };

  spain?: {
    razonSocial: string;
    cifNif?: string;
    cnae?: string;
    cnaeCode?: string;
    legalForm?: SpainLegalForm;
    shareCapital?: number;
  };

  createdAt?: number;
  validationKey: string;
};

type V2CompanyEnrichResult = {
  companies: V2EnrichedCompany[];
  notFoundCompanyIDs: string[];
  insufficientCreditsCompanyIDs: string[];
};
ℹ️

imageFile contains only the file name. To access the logo use CDN: https://cdn.vocations.io/logos/<imageFile>

ℹ️

notFoundCompanyIDs lists IDs that do not exist or are not visible to your key. insufficientCreditsCompanyIDs lists existing companies skipped because credits ran out during the batch: request those again once you have credits. A 402 is returned only when no requested company was affordable.

Possible errors

400
Bad request
Invalid JSON body or invalid payload.
401
Unauthorized
Missing or invalid API key.
429
Rate limit exceeded
500
Internal server error
1.3 Company Status

POST /companies/status

✓ Does not consume credits
Rate limit: max 60 req/min · 3600 req/hour

Checks whether the data you already hold is still current. Send each companyID together with the validationKey returned by /companies/enrich.

Request

request type
type V2CompanyStatusRequest = {
  companies: {
    companyID: string;
    validationKey: string;
  }[];
};

Body example (JSON)

json
{
  "companies": [
    {
      "companyID": "cmp1a2b3c4d5e6f7g",
      "validationKey": "eyJhbGciOiJIUzI1NiJ9..."
    },
    {
      "companyID": "cmp8h9i0j1k2l3m4n",
      "validationKey": "eyJhbGciOiJIUzI1NiJ9..."
    }
  ]
}

Response 200

response
type V2CompanyStatusResult = {
  upToDateIDs: string[];
  outdatedIDs: string[];
  invalidIDs: string[];
};
💡

outdatedIDs contains companies whose data changed since your validationKey was issued: enrich those again. invalidIDs contains unknown companies, or keys that do not belong to the companyID they were sent with.

Possible errors

400
Bad request
Invalid JSON body or invalid payload.
401
Unauthorized
Missing or invalid API key.
429
Rate limit exceeded
500
Internal server error
2.2 Position Enrichment

POST /positions/enrich

💳 1 credit every 5 positions

Request

request type
type V2PositionEnrichRequest = {
  positionIDs: string[];
};

Response 200

response
type V2PositionCompanyContext = {
  companyID: string;
  workAgency: boolean;
  italy?: { ragioneSociale: string; iva?: string };
  spain?: { razonSocial: string; cifNif?: string };
};

type V2EnrichedPosition = {
  positionID: string;

  companyContext: V2PositionCompanyContext;

  name: string;
  description: string;

  urls: string[];
  sources: PositionSource[];

  targets: string[];

  cities: string[];
  provinces: string[];
  regions: string[];

  sites: Location[];

  skills: string[];
  benefits: string[];
  languages: string[];
  certifications: string[];
  courses: string[];

  experience?: ExperienceLevel;
  contract?: Contract;
  hours?: Hours;
  smartWorking: SmartWorking;
  collar?: Collar;
  protectedCategories?: boolean;

  yearlySalaryRange?: YearlySalaryRange;

  createdAt: number;
  republishedAt?: number;

  companyName?: string;

  validationKey: string;
};

type V2PositionEnrichResult = {
  positions: V2EnrichedPosition[];
  failedPositionIDs: string[];
};
ℹ️

To get full company data use endpoint /companies/enrich.

Possible errors

400
Bad request
Invalid JSON body or invalid payload.
401
Unauthorized
Missing or invalid API key.
402
Not enough credits
429
Rate limit exceeded
500
Internal server error
2.3 Position Check

POST /positions/check

✓ Does not consume credits
Rate limit: max 60 req/min · 3600 req/hour

Returns the status of one or more positions.

Request

request type
type V2PositionsCheckRequest = {
  positionIDs: string[];
};

Response 200

response
type V2PositionCheckResult = {
  openIDs: string[];
  closedIDs: string[];
  invalidIDs: string[];
};

Possible errors

400
Bad request
Invalid JSON body or invalid payload.
401
Unauthorized
Missing or invalid API key.
429
Rate limit exceeded
500
Internal server error
3.2 Contact Enrichment

POST /contacts/enrich

💳 3 credits · +2 credits with phone number
Rate limit: max 20 req/min · 600 req/hour

Request

request type
type V2ContactEnrichRequest = {
  contactID: string;
  enrichPhoneNumber?: boolean;
};

Response 200

response
type V2EnrichedContact = {
  contactID: string;

  firstName?: string;
  lastName?: string;

  jobTitle?: string;
  headline?: string;

  linkedInURL?: string;
  photoURL?: string;

  departments: string[];

  email?: string;
  emailStatus?: PendingStatus;

  mobilePhone?: string;
  phoneStatus?: PendingStatus;

  location: LocationLevel[];

  validationKey: string;
};

Possible errors

400
Bad request
Invalid JSON body or invalid payload.
401
Unauthorized
Missing or invalid API key.
402
Not enough credits
404
Contact not found
Contact not found.
429
Rate limit exceeded
500
Internal server error
3.3 Contact Polling

POST /contacts/poll

💳 2 credits when a phone number arrives
Rate limit: max 20 req/min · 600 req/hour

Returns the current state of a contact you already enriched. Use it when enrichPhoneNumber returned phoneStatus: "pending": the phone reveal is asynchronous and lands here once resolved.

Request

request type
type V2ContactPollRequest = {
  contactID: string;
};

Response 200

response
type V2ContactPollResult = {
  email?: string;
  emailStatus?: PendingStatus;

  mobilePhone?: string;
  phoneStatus: PendingStatus;
};
💡

The 2 phone credits are charged only when a number is actually returned. A reveal that ends as notAvailable or failed costs nothing.

Possible errors

400
Bad request
Invalid JSON body or invalid payload.
401
Unauthorized
Missing or invalid API key.
402
Not enough credits
404
Contact not found
Contact not found, or never enriched.
429
Rate limit exceeded
500
Internal server error
4. Usage

Usage

GET /usage

✓ Does not consume credits
Rate limit: max 10 req/min · 300 req/hour

Returns current API key usage status.

GEThttps://vocations.io/api/v2/usage

Response 200

response
type V2UsageSnapshot = {
  usedCredits: number;
  maxCredits: number;

  areCreditsShared: boolean;
  lastResetAt: number;
};

Possible errors

401
Unauthorized
Missing or invalid API key.
429
Rate limit exceeded
500
Internal server error