Поисковый API

Search API — руководство пользователя

POST /api/v6/profiles/search/ — поиск кандидатов в базе AmazingHiring по JSON-запросу: навыки, должности, компании, локации и те же фильтры, что на странице поиска. Результаты не содержат контактов; контакты открываются отдельным запросом GET /api/v6/profiles/{id}/. Слаги сущностей для запроса даёт GET /api/v6/suggestions/.

Документ описывает всё, что нужно, чтобы начать с нуля: доступ, структуру запроса, все поля и фильтры, откуда брать значения, формат ответа, лимиты и ошибки.


1. Доступ

Токен

Токен один на компанию. Его выдаёт администратор компании на странице Company → API management (/company/api-management/) в интерфейсе AmazingHiring. Для этого у компании должен быть включён доступ к API — это делает менеджер AmazingHiring. Поисковая ручка POST /profiles/search/ включается отдельно, тем же менеджером; текущее состояние показано на той же странице API management. Без неё поиск отвечает 402.

Токен передаётся в заголовке Authorization со словом Token:

Authorization: Token a0b1c2d3e4f5...

Токен действует от имени компании и открывает доступ к персональным данным. Храните его как пароль. Перевыпуск токена отключит все интеграции компании.

Базовый URL и документация

ЧтоГде
Продакшенhttps://search.amazinghiring.com
Интерактивная документация (Swagger)https://search.amazinghiring.com/api/v6/docs/
OpenAPI-схема (JSON/YAML)https://search.amazinghiring.com/api/v6/schema/

Схема генерируется из кода и всегда актуальна. Все закрытые списки значений (enum) есть в ней — см. раздел 5.

В Swagger можно вызывать ручки прямо со страницы: нажмите Authorize, выберите TokenAuth, вставьте Token a0b1c2d3e4f5 — дальше Try it out у любой операции. Запросы уходят на тот хост, где открыта документация.

Ограничения

  • Только server-to-server: CORS не настроен, вызывать из браузера нельзя.
  • Запросы синхронные, поиск может занимать несколько секунд.

2. Быстрый старт

Python-разработчики в Берлине и окрестностях с известным email.

Сначала слаг локации — подсказки по началу названия:

curl "https://search.amazinghiring.com/api/v6/suggestions/?type=location&q=berlin" \
  -H "Authorization: Token a0b1c2d3e4f5"
{"results": [{"value": "id-berlin__berlin__germany", "name": "Berlin, Germany"}, ...]}

value первого совпадения — в запрос поиска как есть:

curl -X POST https://search.amazinghiring.com/api/v6/profiles/search/ \
  -H "Authorization: Token a0b1c2d3e4f5" \
  -H "Content-Type: application/json" \
  -d '{
    "skills": {"all": ["python"]},
    "locations": {"any": [{"value": "id-berlin__berlin__germany", "radius_km": 40}]},
    "filters": {"contacts": {"any": ["email"]}},
    "per_page": 20
  }'

Ответ:

{
  "count": 1342,
  "page": 1,
  "per_page": 20,
  "results": [
    {
      "id": 6904,
      "name": "Guido van Rossum",
      "title": "Engineer at Dropbox",
      "locations": [{"id": "berlin__berlin__germany", "name": "Berlin, Germany"}],
      "positions": [...],
      "contacts_opened": false,
      ...
    }
  ]
}

Дальше — GET /api/v6/profiles/6904/, чтобы получить контакты (см. раздел 7).


3. Структура запроса

{
  "skills":         {"all": [...], "any": [...], "none": [...], "preferred": [...]},
  "positions":      {"any": [...], "none": [...]},
  "last_positions": {"any": [...], "none": [...]},
  "companies":      {"any": [...], "none": [...]},
  "last_companies": {"any": [...], "none": [...]},
  "locations":      {"any": [...], "none": [...]},
  "educations":     {"any": [...], "none": [...]},
  "names":          {"any": [...], "none": [...]},
  "text":           {"any": [...], "none": [...]},

  "filters": { ... },

  "page": 1,
  "per_page": 50
}

Три части:

  1. Термы (skills, positions, … text) — что ищем. Обязателен хотя бы один положительный терм (all, any или preferred) в любой группе. Запрос только с фильтрами вернёт 400.
  2. Фильтры (filters) — как сужаем. Все опциональны.
  3. Пагинация (page, per_page).

Запрос строгий: неизвестный ключ на любом уровне — ошибка 400 с текстом Unknown field.. Опечатка не превратится молча в пустую выдачу.


4. Термы

4.1. Что такое терм

Терм — строка либо объект {"value": "...", ...}. Две формы равнозначны, объект нужен, когда есть уточнение (min_years, radius_km).

"python"
{"value": "python"}
{"value": "id-python", "min_years": 3}

Значение — одно из двух:

ВидПримерКак ищется
Слаг известной сущности — начинается с id-id-python, id-google, id-berlin__berlin__germanyТочное совпадение с сущностью базы: все синонимы и написания. Надёжнее.
Свободный текстpython, backend engineer, machine learningПолнотекстовый поиск. Несколько слов ищутся как фраза.

Откуда брать слаги — раздел 5.2.

Ограничения на значение:

  • от 1 до 100 символов;
  • без запятой;
  • не начинается с -.

В каждом списке (all, any, …) — не больше 50 термов.

4.2. Логика операторов

КлючСмыслГде доступен
allКаждый терм должен совпасть (AND)только skills
anyХотя бы один терм должен совпасть (OR)все группы
noneНи один не должен совпасть (NOT)все группы
preferredНе фильтрует, только поднимает совпадения выше в выдачетолько skills

Разные группы объединяются через AND: skills.all = [python] + locations.any = [id-germany] — Python и Германия.

4.3. Группы термов

ГруппаЧто ищемОсобенности
skillsНавыкиall поддерживает min_years
positionsДолжности за всю карьеру
last_positionsТекущая должность
companiesКомпании за всю карьеру
last_companiesТекущая компания
locationsМесто жительства: страна, регион или городany поддерживает radius_km
educationsУчебные заведения
namesИмя и фамилия
textСвободный текст по всему профилю

4.4. Уточнения термов

min_years — минимальный стаж с навыком. Только в skills.all. Допустимые значения: 2, 3, 4, 5.

"skills": {"all": [{"value": "id-go", "min_years": 3}]}

radius_km — искать также в радиусе от города. Только в locations.any, только для городов (для страны или региона игнорируется). Допустимые значения: 20, 40, 80, 160.

"locations": {"any": [{"value": "id-munich__bavaria__germany", "radius_km": 80}]}

Другие значения min_years / radius_km отклоняются с 400.


5. Фильтры

Объект filters. Все поля опциональны.

5.1. Полный список

Диапазоны

Формат: {"min": N, "max": N, "include_unknown": bool}. Любая граница опциональна, обе включительно. min > max — ошибка. include_unknown: true добавляет в выдачу профили, у которых значение неизвестно.

ПолеЕдиница
ageлет
experience_yearsобщий стаж, лет
months_on_last_positionмесяцев на текущей позиции
graduation_yearкалендарный год выпуска
"experience_years": {"min": 5},
"age": {"min": 25, "max": 45, "include_unknown": true}

Закрытые списки (enum) — {"any": [...], "none": [...]}

any — хотя бы одно из значений, none — ни одного.

ПолеЗначенияПримечание
contactsemail, phone, messengers, any, noany — есть хоть какой-то контакт, no — контактов нет вообще
seniorityjunior, middle, senior, team_lead, head_of, c_level, any, noУровень текущей позиции. any — уровень известен, no — неизвестен
education_levelsbachelor, master, specialist, doctorate, csdegreeВысшая степень. csdegree — степень в Computer Science любого уровня
company_size1-10, 11-200, 201-500, 501-1000, 1001-5000, 5001-10'000, 10'000+Плюс поле scope, см. ниже
genderfemaleЗависит от GDPR-настроек компании
diversityasian, black, hispanic, indian, middleEast, whiteЗависит от GDPR-настроек компании

company_size дополнительно принимает scope — какие компании учитывать:

scopeСмысл
currentтекущая компания (по умолчанию)
previousпредыдущие компании
anyлюбая
"company_size": {"any": ["201-500", "501-1000"], "scope": "any"}

Открытые списки — {"any": [...], "none": [...]}

Значения — идентификаторы из базы AmazingHiring (раздел 5.2).

ПолеЧтоПример значения
industriesИндустрии компанийsoftware engineering
companiesКомпании за всю карьеру, по слагуid-google
last_companiesТекущая компания, по слагуid-microsoft
educationsУчебные заведения, по слагуid-stanford-university

Чем filters.companies отличается от терма companies: терм — поле поисковой строки (слаг или свободный текст), фильтр — чекбоксы фильтр-панели UI, принимает только идентификаторы из неё. Если знаете слаг — используйте терм; фильтр нужен, чтобы воспроизвести запрос, собранный через панель фильтров.

Источники профиля — sites

{"all": [...], "none": [...]} — домены, с которых собран профиль. all — профиль есть на каждом из перечисленных (AND), none — ни на одном.

"sites": {"all": ["github.com", "stackoverflow.com"], "none": ["linkedin.com"]}

Языки — languages

Список объектов. Каждый перечисленный язык обязателен (AND между языками); уровни внутри языка — OR.

"languages": [
  {"name": "English", "levels": ["full", "native"]},
  {"name": "German"}
]
ПолеЗначения
nameНазвание языка по-английски: English, German, Spanish
levelselem (elementary), limited (limited working), prof (professional working), full (full professional), native, unknown. Опционально — без levels подходит любой уровень

Флаги — true / false

Полеtrue означает
remoteОткрыт к удалённой работе
freelancerФрилансер
frequent_job_changerМеняет работу чаще обычного
exclude_multiple_locationsИсключить профили, у которых источники расходятся в локации
exclude_linkedinИсключить профили, найденные только на LinkedIn

false — явно требовать обратного. Чтобы не фильтровать по флагу, просто не передавайте его.

5.2. Откуда брать значения

Закрытые списки

Значения перечислены выше и зафиксированы в OpenAPI-схеме GET /api/v6/schema/ — раздел components.schemas:

СхемаПоле
ContactKindEnumfilters.contacts
SeniorityEnumfilters.seniority
EducationLevelEnumfilters.education_levels
CompanySizeEnum, CompanySizeScopeEnumfilters.company_size
GenderEnumfilters.gender
DiversityEnumfilters.diversity
LanguageLevelEnumfilters.languages[].levels

Допустимые min_years и radius_km — в схемах SkillTermField / LocationTermField (тоже enum). Схема — источник истины: если список расширится, он изменится там же.

Слаги (id-...)

Слаг = id- + идентификатор сущности в базе AmazingHiring. Способы получить:

  1. Подсказки — GET /api/v6/suggestions/ (раздел 5.3). Основной способ: передаёте начало названия и тип сущности, получаете список известных сущностей с готовым термом value.

  2. Из результатов поиска. Идентификаторы в ответе — это те же сущности без префикса:

    • results[].locations[].id"berlin__berlin__germany" → терм id-berlin__berlin__germany;
    • results[].positions[].company.id"yandex" → терм id-yandex.
  3. Из адресной строки страницы поиска. Соберите запрос в интерфейсе AmazingHiring через подсказки — URL страницы результатов содержит параметры q= с теми же слагами (location[0]:id-united-states, skillAll[0]:id-python).

  4. Свободный текст. Если слаг неизвестен, передайте название как текст: "python", "google", "berlin". Работает, но ловит только буквальное совпадение написания.

Формат слагов локаций — город__регион__страна, для страны — просто id-germany, для региона — id-bavaria__germany. Слаги компаний и учебных заведений — латиница через дефис: id-google, id-stanford-university.

Неизвестный слаг не вызывает ошибку — он просто ничему не соответствует, и выдача будет пустой или неполной. Проверяйте count.

Индустрии, сайты, языки

  • industries — названия строчными буквами по-английски, как в фильтр-панели UI: software engineering, fintech. Публичного списка нет (в /suggestions/ индустрий нет); берите из URL страницы поиска (f=industry[0]:...).
  • sites — доменные имена: github.com, stackoverflow.com, linkedin.com, habr.com.
  • languages[].name — английское название языка с большой буквы.

5.3. Подсказки — GET /suggestions/

Автокомплит сущностей, которые понимает поиск. Передаёте тип и начало названия — получаете известные сущности, лучшие совпадения первыми.

curl "https://search.amazinghiring.com/api/v6/suggestions/?type=location&q=berl" \
  -H "Authorization: Token a0b1c2d3e4f5"
{
  "results": [
    {"value": "id-berlin__berlin__germany", "name": "Berlin, Germany"},
    {"value": "id-berlin__coos-county__new-hampshire__united-states", "name": "Berlin, New Hampshire, USA"}
  ]
}
ПараметрОбязательныйЗначения
typeдаskill, position, company, location, education
qдаНачало названия, 1–100 символов, без ,
typeЧто подсказываетКуда подставлять value
skillНавыки и технологииskills.all/any/none/preferred
positionДолжностиpositions, last_positions
companyКомпанииcompanies, last_companies, filters.companies, filters.last_companies
locationГорода, регионы, страныlocations
educationУчебные заведенияeducations, filters.educations
  • value — готовый терм: подставляйте в запрос поиска как есть, без обработки. Для локаций, компаний и вузов это слаг id-..., для навыков и должностей — каноническое название, которое поиск сопоставляет с сущностью, а не с написанием.
  • name — человекочитаемое название для показа пользователю.
  • Пустой results — ничего не найдено. Язык подсказок определяется языком пользователя токена.
  • Подсказки не тратят дневной лимит поиска. Кэшируйте результаты на своей стороне: справочник меняется редко.

6. Ответ

{
  "count": 1342,
  "page": 1,
  "per_page": 50,
  "results": [ { ...профиль... } ]
}
ПолеСмысл
countОбщее число подходящих профилей
page, per_pageКак в запросе (или значения по умолчанию)
resultsПрофили текущей страницы

Поля профиля в выдаче

Та же структура, что у GET /api/v6/profiles/{id}/, минус contacts и comments, плюс contacts_opened.

ПолеТипОписание
idintИдентификатор профиля — для GET /profiles/{id}/
namestringИмя
titlestring | nullЗаголовок профиля (обычно «должность at компания»)
general_infostring | nullКраткое описание
ageint | nullВозраст
birthdaystringYYYY-MM-DD, пустая строка если дата неизвестна
avatarsstring[] | nullURL фотографий
languagesobject{"English": "native", ...}
locationsobject[]{id, name} — id пригоден как слаг с префиксом id-
positionsobject[]{position, description, company: {id, name, site}, start, end, skills}; текущие идут первыми; даты YYYY-MM
educationsobject[]{name, faculty, specialization, degree, start, end}
courses_or_certificatesobject[]{name, organization_name, start, end}
skillsobject[]Только языки программирования: {name, sources[], additional_skills[]}
all_skills_groupedobject[]Все навыки по группам: {id, name, skills[]}
linksobject[]{value, personal_site} — ссылки на источники
resumesobject[]Резюме, загруженные вашей компанией
contacts_openedboolКонтакты уже открыты вашей компанией — GET /profiles/{id}/ бесплатен

Ссылки замаскированы. По умолчанию links[].value — не прямой URL источника, а прокси-ссылка вида https://search.amazinghiring.com/api/profiles/{id}/links/{hash}/. Она редиректит на оригинал при открытии. Это настройка компании; чтобы получать прямые URL, обратитесь к менеджеру AmazingHiring.

Профили, запросившие удаление данных (opt-out), в выдачу не попадают — поэтому в странице может оказаться меньше per_page элементов при непустых следующих страницах.


7. Открытие контактов

Контакты — платная часть. Поиск их не возвращает.

curl https://search.amazinghiring.com/api/v6/profiles/6904/ \
  -H "Authorization: Token a0b1c2d3e4f5"

Ответ — полный профиль с полем contacts:

"contacts": [
  {"type": "email", "value": "[email protected]"},
  {"type": "phone", "value": "+1..."},
  {"type": "skype", "value": "..."}
]

Правила списания:

  • Если в выдаче поиска contacts_opened: true — контакты уже открыты вашей компанией, запрос бесплатен.
  • Если false — списывается 1 кредит квоты контактов компании. Повторные запросы того же профиля бесплатны.
  • Отдельно действует дневной лимит открытых через API профилей (по умолчанию 400 на компанию в день).
  • Лимит исчерпан → 402 (см. раздел 9).

Запрос GET /profiles/{id}/ — это и есть «открыть контакты». Не вызывайте его «просто посмотреть»: списание происходит на GET.

Проверить, стоит ли открывать, можно по фильтру contacts в самом поиске: {"contacts": {"any": ["email"]}} вернёт только профили с известным email.


8. Пагинация и лимит поиска

ПараметрПо умолчаниюДиапазон
page1≥ 1
per_page501–100

Обход всей выдачи: увеличивайте page, пока page * per_page < count.

Каждая полученная страница расходует дневной лимит результатов поиска компании (по умолчанию 15 000 профилей в день). Списывается количество реально возвращённых профилей. При исчерпании — 429, лимит сбрасывается через сутки после последнего запроса.

Практика:

  • Берите per_page: 100 — лимит расходуется на профили, а не на запросы, крупные страницы просто быстрее.
  • Повтор байт-в-байт идентичного запроса (те же термы, фильтры, page, per_page) лимит повторно не расходует — запись в истории перезаписывается. Любое отличие — уже новый запрос.
  • Сужайте запрос фильтрами до нужного count, а не выгружайте всё подряд.

Лимит считается по пользователю токена. Токен компании — отдельный служебный пользователь, поэтому ручной поиск сотрудников в интерфейсе не мешает интеграции и наоборот.

GET /suggestions/ в лимит результатов не входит.


9. Ошибки

КодПричинаЧто делать
400Невалидный запрос: неизвестное поле, неверное значение enum, min > max, нет ни одного положительного терма, per_page > 100Исправить запрос. Тело описывает проблемное поле
401Нет заголовка Authorization, токен неверный или у компании нет доступа к APIПроверить токен
403Нет активной лицензии, либо запрет GDPR (см. ниже)Обратиться к администратору компании
402Исчерпана квота контактов или дневной лимит профилей через API (GET /profiles/{id}/), либо Search API не включён для компании (POST /profiles/search/, GET /suggestions/, код 1012)Ждать сброса / увеличить квоту; для 1012 — обратиться к менеджеру AmazingHiring
429Исчерпан дневной лимит результатов поискаПовторить через сутки
502Поисковый бэкенд недоступен или вернул ошибкуПовторить с экспоненциальной задержкой (1 с, 2 с, 4 с…)
504Поисковый бэкенд не ответил вовремяПовторить с задержкой; упростить запрос

Формат ошибок валидации (400)

Стандартный: ключ — путь до поля, значение — список сообщений.

{"skill": ["Unknown field."]}
{"skills": {"all": {"0": ["\"min_years\" must be one of [2, 3, 4, 5]."]}}}
{"non_field_errors": ["At least one term to search for is required."]}
{"filters": {"seniority": {"any": {"0": ["\"lead\" is not a valid choice."]}}}}

Формат ошибок лимитов и доступа

{"status": {"code": 1010, "message": "Request was throttled."}}
status.codeHTTPСмысл
1010429Дневной лимит результатов поиска исчерпан
1011402Квота контактов / профилей исчерпана
1012402Search API не включён для компании
100403GDPR: доступ к профилю ограничен
102403GDPR: фильтр age запрещён для вашей компании
103403GDPR: фильтр gender запрещён
104403GDPR: фильтр diversity запрещён

Фильтры age, gender, diversity доступны не всем компаниям — это регулируется GDPR-настройками аккаунта. Если ловите 102104, уберите фильтр или обсудите настройки с менеджером.


10. Примеры

Senior backend на Go, не из Google, говорит по-английски

{
  "skills": {
    "all": [{"value": "id-go", "min_years": 3}, "kubernetes"],
    "any": ["postgresql", "mysql"],
    "none": ["php"],
    "preferred": ["grpc"]
  },
  "last_positions": {"any": ["backend engineer", "backend developer"], "none": ["intern"]},
  "companies": {"none": ["id-google"]},
  "filters": {
    "seniority": {"any": ["senior", "team_lead"]},
    "experience_years": {"min": 5},
    "months_on_last_position": {"min": 12},
    "languages": [{"name": "English", "levels": ["full", "native"]}],
    "remote": true
  },
  "per_page": 100
}

Data scientist из топ-вузов, с телефоном, активен на GitHub

{
  "positions": {"any": ["data scientist", "ml engineer"]},
  "educations": {"any": ["id-mit", "id-stanford-university"]},
  "filters": {
    "contacts": {"any": ["phone"]},
    "education_levels": {"any": ["master", "doctorate"]},
    "sites": {"all": ["github.com"]}
  }
}

Все, кто сейчас работает в конкретной компании

{
  "last_companies": {"any": ["id-yandex"]},
  "per_page": 100
}

Оценить объём до выгрузки

Запрос с per_page: 1 списывает из лимита один профиль, а count возвращает полный объём выдачи.

{
  "skills": {"all": ["id-rust"]},
  "locations": {"any": ["id-germany"]},
  "per_page": 1
}

11. Рекомендации по интеграции

  • Ретраи только на 502/504 и сетевые ошибки, с экспоненциальной задержкой и потолком в 3–5 попыток. 4xx не ретраить.
  • Логируйте тело ошибок 400 — они точно указывают на поле.
  • Slug > текст. Где известен слаг, используйте его: текстовое совпадение зависит от написания в источнике. Слаги берите из GET /suggestions/ и кэшируйте — справочник меняется редко.
  • Проверяйте count после смены слагов: опечатка в id-... не даёт ошибки, а даёт пустую выдачу.
  • Следите за схемой GET /api/v6/schema/ при обновлениях: новые фильтры и значения появляются там первыми.