Поисковый 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
}
Три части:
- Термы (
skills,positions, …text) — что ищем. Обязателен хотя бы один положительный терм (all,anyилиpreferred) в любой группе. Запрос только с фильтрами вернёт400. - Фильтры (
filters) — как сужаем. Все опциональны. - Пагинация (
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 — ни одного.
| Поле | Значения | Примечание |
|---|---|---|
contacts | email, phone, messengers, any, no | any — есть хоть какой-то контакт, no — контактов нет вообще |
seniority | junior, middle, senior, team_lead, head_of, c_level, any, no | Уровень текущей позиции. any — уровень известен, no — неизвестен |
education_levels | bachelor, master, specialist, doctorate, csdegree | Высшая степень. csdegree — степень в Computer Science любого уровня |
company_size | 1-10, 11-200, 201-500, 501-1000, 1001-5000, 5001-10'000, 10'000+ | Плюс поле scope, см. ниже |
gender | female | Зависит от GDPR-настроек компании |
diversity | asian, 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… |
levels | elem (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:
| Схема | Поле |
|---|---|
ContactKindEnum | filters.contacts |
SeniorityEnum | filters.seniority |
EducationLevelEnum | filters.education_levels |
CompanySizeEnum, CompanySizeScopeEnum | filters.company_size |
GenderEnum | filters.gender |
DiversityEnum | filters.diversity |
LanguageLevelEnum | filters.languages[].levels |
Допустимые min_years и radius_km — в схемах SkillTermField / LocationTermField (тоже enum). Схема — источник истины: если список расширится, он изменится там же.
Слаги (id-...)
Слаг = id- + идентификатор сущности в базе AmazingHiring. Способы получить:
-
Подсказки —
GET /api/v6/suggestions/(раздел 5.3). Основной способ: передаёте начало названия и тип сущности, получаете список известных сущностей с готовым термомvalue. -
Из результатов поиска. Идентификаторы в ответе — это те же сущности без префикса:
results[].locations[].id→"berlin__berlin__germany"→ термid-berlin__berlin__germany;results[].positions[].company.id→"yandex"→ термid-yandex.
-
Из адресной строки страницы поиска. Соберите запрос в интерфейсе AmazingHiring через подсказки — URL страницы результатов содержит параметры
q=с теми же слагами (location[0]:id-united-states,skillAll[0]:id-python). -
Свободный текст. Если слаг неизвестен, передайте название как текст:
"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.
| Поле | Тип | Описание |
|---|---|---|
id | int | Идентификатор профиля — для GET /profiles/{id}/ |
name | string | Имя |
title | string | null | Заголовок профиля (обычно «должность at компания») |
general_info | string | null | Краткое описание |
age | int | null | Возраст |
birthday | string | YYYY-MM-DD, пустая строка если дата неизвестна |
avatars | string[] | null | URL фотографий |
languages | object | {"English": "native", ...} |
locations | object[] | {id, name} — id пригоден как слаг с префиксом id- |
positions | object[] | {position, description, company: {id, name, site}, start, end, skills}; текущие идут первыми; даты YYYY-MM |
educations | object[] | {name, faculty, specialization, degree, start, end} |
courses_or_certificates | object[] | {name, organization_name, start, end} |
skills | object[] | Только языки программирования: {name, sources[], additional_skills[]} |
all_skills_grouped | object[] | Все навыки по группам: {id, name, skills[]} |
links | object[] | {value, personal_site} — ссылки на источники |
resumes | object[] | Резюме, загруженные вашей компанией |
contacts_opened | bool | Контакты уже открыты вашей компанией — 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. Пагинация и лимит поиска
| Параметр | По умолчанию | Диапазон |
|---|---|---|
page | 1 | ≥ 1 |
per_page | 50 | 1–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.code | HTTP | Смысл |
|---|---|---|
1010 | 429 | Дневной лимит результатов поиска исчерпан |
1011 | 402 | Квота контактов / профилей исчерпана |
1012 | 402 | Search API не включён для компании |
100 | 403 | GDPR: доступ к профилю ограничен |
102 | 403 | GDPR: фильтр age запрещён для вашей компании |
103 | 403 | GDPR: фильтр gender запрещён |
104 | 403 | GDPR: фильтр diversity запрещён |
Фильтры age, gender, diversity доступны не всем компаниям — это регулируется GDPR-настройками аккаунта. Если ловите 102–104, уберите фильтр или обсудите настройки с менеджером.
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/при обновлениях: новые фильтры и значения появляются там первыми.