Преди да започнете
Има два начина за интеграция. Те ползват един партньорски запис, един публичен ключ, един токен за вход и едни и същи лимити.
- Вграждане: един таг script или същият адрес в web view на приложение. Нашите компоненти с вашия акцентен цвят и език. Входът, съгласието, попълването на профила и „Проявявам интерес“ стават в рамката.
- API без интерфейс: JSON адреси, които извиквате с ключа си, а интерфейса правите вие. Достъпни са обявите, входът, проявата на интерес и чатът с ИИ за профила.
| Данни за достъп | Къде стоят | За какво служат |
|---|---|---|
Публичен ключ pk_… | в страниците и приложенията ви; вижда се в кода на страницата | идентифицира ви: настройки, указване на източника, обхват и лимити |
| Сървърна тайна (64 шестнадесетични знака) | само на вашия сървър | подписва токените за вход и чете статистиката; след смяна старата работи още 10 минути |
Jobyhood записва и разрешените ви адреси (https://news.example), които решават кой може да вгражда рамката и да вика API от браузър; държава по подразбиране и филтри по желание; темата ви; дали входът е включен; и дали Jobyhood се доверява на проверката ви на имейла, без която не се създават акаунти.
Поискайте партньорски запис чрез формата на страницата за партньори. След това екипът ви управлява ключове, сайтове, тема и членове в партньорското табло.
Вграждане
Поставете скрипта там, където искате обявите. Той вмъква iframe на това място и поддържа височината му според съдържанието.
<script src="https://jobyhood.com/embed/v1.js"
data-key="pk_your_public_key" data-lang="bg" data-theme="auto"
data-accent="#0C68E9" data-filters="city=Sofia" async></script>
<script>
window.addEventListener('load', () => {
// {kind}; matches_available also carries {count} (a number only).
JobyhoodJobs.onEvent = (event) => {
if (event.kind === 'matches_available') showBadge(event.count);
};
// "Continue with …" and no token yet: mint one on your server, then hand it over.
JobyhoodJobs.onNeedsToken = async () => {
const response = await fetch('/jobyhood-token', { credentials: 'include' });
const { token } = await response.json();
JobyhoodJobs.setToken(token);
};
});
</script>| Атрибут | Стойности | Бележки |
|---|---|---|
data-key | публичният ви ключ | задължителен |
data-lang | bg, en | по подразбиране: езикът на браузъра на посетителя |
data-theme | light, dark, auto | auto следва системната настройка на посетителя |
data-accent | #rrggbb | бутони и акценти |
data-radius | 0–32 | заобляне на ъглите в пиксели |
data-country | двубуквен код, например BG | държавата на обявите |
data-filters | q=…&role=…&city=…&country=… | началните филтри; посетителят пак може да търси |
data-job | номер на обява | отваря първо тази обява |
data-token | токен за вход | вписва посетителя при зареждане (Токен за вход) |
data-on-event | име на глобална функция | извиква се като onEvent |
API за JavaScript
| След зареждане | Какво прави |
|---|---|
JobyhoodJobs.setToken(token) | предава токен за вход на рамката |
JobyhoodJobs.open(jobId) | отваря една обява |
JobyhoodJobs.setFilters({city: "Varna"}) | сменя филтрите на списъка (q, role, city, country) |
JobyhoodJobs.onEvent = (event) => … | получава всяко от събитията по-долу |
JobyhoodJobs.onNeedsToken = () => … | извиква се, когато посетителят натисне „Продължи с {вашето име}“ без подаден токен; отговорете със setToken |
Събития
kind | Кога |
|---|---|
signup | създаден е акаунт чрез вашия вход |
login | съществуващ потребител на Jobyhood е влязъл чрез вас |
interest | посетителят е проявил интерес към обява |
needs-token | рамката е поискала токен за вход (извиква се и onNeedsToken) |
matches_available | броят съвпадения на влязъл член е известен или се е променил; носи count и нищо друго |
Събитията не носят лични данни: нито имена, нито имейли, номера на обяви или оценки.
Собствен iframe
Без скрипта вградете сами https://jobyhood.com/embed/jobs и общувайте с рамката чрез postMessage. Рамката изпраща jobyhood:resize {height}, jobyhood:event {kind, count} и jobyhood:needs-token към страницата, която я вгражда. Тя приема jobyhood:token {token} и jobyhood:filters {filters} само от тази страница и само от някой от регистрираните ви адреси.
// <iframe id="jobyhood" src="https://jobyhood.com/embed/jobs?key=pk_your_public_key&lang=bg"
// title="Jobs" style="width:100%;border:0"></iframe>
const JOBYHOOD = 'https://jobyhood.com';
const frame = document.getElementById('jobyhood');
window.addEventListener('message', async (event) => {
if (event.origin !== JOBYHOOD || event.source !== frame.contentWindow) return;
const message = event.data || {};
if (message.type === 'jobyhood:resize') {
frame.style.height = `${Math.max(240, Math.ceil(message.height))}px`;
} else if (message.type === 'jobyhood:event') {
track(message.kind, message.count);
} else if (message.type === 'jobyhood:needs-token') {
const { token } = await fetch('/jobyhood-token', { credentials: 'include' }).then((r) => r.json());
frame.contentWindow.postMessage({ type: 'jobyhood:token', token }, JOBYHOOD);
}
});
// Later, to change what the list shows:
frame.contentWindow.postMessage({ type: 'jobyhood:filters', filters: { city: 'Varna' } }, JOBYHOOD);Вашите съвпадения
Посетител, влязъл чрез вас, вижда „Вашите съвпадения“ над списъка с обяви. Бутонът отваря /embed/matches, който можете да отворите и директно с ключа си. Там са обявите, които Jobyhood е намерил за него и които вашето вграждане може да покаже (отворени обяви, в обхвата на вашата компания, ако имате такъв), най-новите първо, без оценки.
Списъкът остава в рамката на Jobyhood. Страницата ви получава само {kind: "matches_available", count}, веднъж щом броят е известен и отново при промяна, за да покажете брояч. До вас не стигат номера, заглавия или оценки на обяви.
Токен за вход
Вашият сървър подписва краткотраен JWT (HS256, със сървърната тайна) за потребител, когото вече е удостоверил, а страницата или приложението ви го предава на Jobyhood. Създавайте го точно преди предаването: живее най-много 10 минути и работи веднъж.
| Поле | Правило |
|---|---|
iss | идентификаторът ви на партньор (slug) |
aud | "jobyhood" |
sub | постоянният номер на потребителя при вас, до 255 знака |
iat, exp | най-много 600 секунди разлика, с 60 секунди толеранс за часовника |
jti | уникален за всеки токен; всеки токен работи веднъж |
email | задължително |
email_verified | true; нужно е за създаване на акаунт и Jobyhood трябва да се доверява на проверката ви |
given_name, family_name, phone, locale | по желание |
import crypto from 'node:crypto';
import jwt from 'jsonwebtoken';
// GET /jobyhood-token, for your signed-in user only. The secret never leaves your server.
app.get('/jobyhood-token', requireLogin, (req, res) => {
const token = jwt.sign(
{
sub: String(req.user.id),
email: req.user.email,
email_verified: true,
given_name: req.user.firstName,
locale: 'bg',
jti: crypto.randomUUID(),
},
process.env.JOBYHOOD_SERVER_SECRET,
{ algorithm: 'HS256', issuer: 'your-partner-slug', audience: 'jobyhood', expiresIn: '5m' }
);
res.set('Cache-Control', 'no-store').json({ token });
});import os
import time
import uuid
import jwt # PyJWT
def jobyhood_token(user) -> str:
"""A single-use sign-in token for a user you have already authenticated."""
now = int(time.time())
claims = {
"iss": "your-partner-slug",
"aud": "jobyhood",
"sub": str(user.id),
"email": user.email,
"email_verified": True,
"given_name": user.first_name,
"locale": "bg",
"iat": now,
"exp": now + 300,
"jti": str(uuid.uuid4()),
}
return jwt.encode(claims, os.environ["JOBYHOOD_SERVER_SECRET"], algorithm="HS256")Вграждането взема токена от data-token, от съобщение jobyhood:token от вашата страница или, в приложение, от setToken. #token= в адреса важи само във вашата рамка или в приложение с мост, никога в обикновен раздел на браузъра. Без интерфейс го обменяте сами: първо покажете екрана за съгласие и изпратете версията му.
POST https://api.jobyhood.com/v1/partners/sso/
X-Jobyhood-Partner-Key: pk_your_public_key
Content-Type: application/json
{"token": "<jwt>", "consent_version": "<consentVersion from config>"}
200 {"access": "…", "refresh": "…", "created": true, "needsOnboarding": true,
"user": {"id": 1, "email": "…", "firstName": "…"}}
400 {"code": "consent_required"}Откази
| Код | HTTP статус | Какво да направите |
|---|---|---|
invalid_token, expired | 400 | създайте нов токен |
replayed | 409 | създайте нов токен; всеки jti работи веднъж |
consent_required | 400 | покажете екрана за съгласие с текущата consentVersion и опитайте отново |
email_not_verified | 403 | имейлът не е потвърден или Jobyhood още не се доверява на проверката ви: предложете регистрация в jobyhood.com |
email_in_use | 409 | имейлът вече има акаунт в Jobyhood: свържете го; токенът не се изразходва |
email_mismatch | 400 | само при link/: имейлът на влезлия потребител се различава от този в токена |
account_linked_elsewhere | 409 | този sub вече е свързан с друг профил в Jobyhood |
company_account, account_unavailable | 403 | link/ и повторен вход: акаунтът е фирмен или блокиран (sso/ отговаря email_in_use за всеки съществуващ акаунт, включително фирмен) |
sso_disabled | 403 | свържете се с Jobyhood |
idp_unavailable | 502 | само при OpenID Connect: документът за откриване или ключовете ви не могат да се прочетат |
Вместо това: OpenID Connect
Ако сървърът ви не може да създава HS256 токени, но ползвате доставчик на OpenID Connect, Jobyhood може да приеме неговия RS256 id_token в същото поле token на sso/ и link/. Jobyhood записва адреса на издателя ви и идентификатора на клиента, за когото трябва да е издаден токенът.
issе издателят ви, аaudе идентификаторът на клиента.iatиexp: валиден сега и издаден през последните 600 секунди, с 60 секунди толеранс. Искайте нов токен за предаването.nonceилиjtiе задължително и токенът работи веднъж.emailе задължително, аemail_verifiedтрябва да е булевотоtrue.given_name,family_name,phone_numberиlocaleса по желание.- Документът за откриване трябва да назовава същия издател. Той и неговият
jwks_uriтрябва да са публични https адреси, а пренасочвания не се следват.
Всичко след проверката е същото: съгласието, еднократната употреба, правилата за акаунта и отговорът. Щом това е настроено, config показва oidcIssuer.
Съгласие
Преди да се създаде акаунт чрез вас, човекът вижда това на своя език, с връзки към Условията и Политиката за поверителност и поле за отметка:
Вграждането го показва вместо вас. Без интерфейс го показвате сами и изпращате consentVersion от config/ като consent_version. Хората могат да прекратят връзката с вас по всяко време от настройките си в Jobyhood.
Свързване на съществуващ акаунт
Когато имейлът вече има акаунт в Jobyhood, sso/ отговаря с email_in_use и не изразходва токена. Във вграждането посетителят натиска „Влезте в Jobyhood и свържете“: отваря се изскачащ прозорец за вход в Jobyhood, човекът влиза и потвърждава, а рамката свързва вашата самоличност с този акаунт. Нищо за акаунта не стига до страницата ви.
Без интерфейс: нека човекът влезе в jobyhood.com, след това извикайте link/ с неговия токен за достъп в Jobyhood и същия токен, докато още е валиден. Имейлите трябва да съвпадат, иначе отговорът е email_mismatch.
POST https://api.jobyhood.com/v1/partners/link/
X-Jobyhood-Partner-Key: pk_your_public_key
Authorization: Bearer <the user’s Jobyhood access token>
Content-Type: application/json
{"token": "<the same jwt>", "consent_version": "<consentVersion from config>"}Мобилни приложения
Приложенията отварят самото вграждане на цял екран в web view. Не ви трябва собствена страница: приложението говори с вграждането през малък мост, който само добавя.
Отворете вграждането
Заредете адреса на вграждането с ключа и езика. Ако вече имате токен, когато изгледът се отваря, сложете го във фрагмента: фрагментът никога не стига до сървър, дневник или заглавка Referer, а вграждането го прочита веднъж и го маха от адреса. Токен в заявката (след „?“) се пренебрегва.
https://jobyhood.com/embed/jobs?key=pk_your_public_key&lang=bgМостът: събития и токен при поискване
Добавете JobyhoodApp на Android (addJavascriptInterface, с метод postMessage(String)) или обработчик на съобщения с име jobyhood на iOS, достъпен като webkit.messageHandlers.jobyhood. Когато вграждането е основната страница и намери моста, то изпраща на приложението съобщенията, които иначе получава страницата домакин, като JSON текст:
| Съобщение | Кога |
|---|---|
jobyhood:needs-token | посетителят е натиснал „Продължи с …“, а вграждането още няма токен |
jobyhood:event {kind} | във вграждането е станало signup, login или interest |
jobyhood:event {kind, count} | matches_available: колко съвпадения има влезлият член, за значка |
Отговорете на jobyhood:needs-token, като изпълните JobyhoodJobs.setToken(token) в web view; функцията връща true, когато токенът е приет. Тя съществува само в този случай, вграждане като основна страница с мост: вграждане в рамка приема токени само от страницата домакин. Няма jobyhood:resize, защото web view е самата страница. Без мост вграждането не изпраща нищо, а „Продължи с …“ работи само с токен от адреса.
class JobsActivity : AppCompatActivity() {
private lateinit var web: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
web = WebView(this).also(::setContentView)
web.settings.javaScriptEnabled = true
web.settings.domStorageEnabled = true // the embed keeps its session here
web.settings.setSupportMultipleWindows(true) // the "connect your account" pop-up
web.addJavascriptInterface(Bridge(), "JobyhoodApp") // events; e-mail-only sign-in in the pop-up
web.webChromeClient = Popups()
web.loadUrl("https://jobyhood.com/embed/jobs?key=pk_your_public_key&lang=bg")
}
inner class Bridge {
@JavascriptInterface
fun postMessage(json: String) {
val message = JSONObject(json)
when (message.getString("type")) {
"jobyhood:needs-token" -> lifecycleScope.launch {
val token = yourBackend.jobyhoodToken() // see "Sign-in token"
web.evaluateJavascript("JobyhoodJobs.setToken(${JSONObject.quote(token)})", null)
}
"jobyhood:event" -> if (message.getString("kind") == "matches_available") {
runOnUiThread { showBadge(message.getInt("count")) }
}
}
}
}
// The pop-up opens in a web view of its own, so it can hand the session back to the embed.
inner class Popups : WebChromeClient() {
override fun onCreateWindow(view: WebView, dialog: Boolean, gesture: Boolean, msg: Message): Boolean {
val popup = WebView(this@JobsActivity).apply {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
webViewClient = WebViewClient()
webChromeClient = this@Popups
}
popup.tag = Dialog(this@JobsActivity).apply { setContentView(popup); show() }
(msg.obj as WebView.WebViewTransport).webView = popup
msg.sendToTarget()
return true
}
override fun onCloseWindow(window: WebView) {
(window.tag as? Dialog)?.dismiss()
window.destroy()
}
}
}import UIKit
import WebKit
final class JobsViewController: UIViewController, WKScriptMessageHandler, WKUIDelegate {
private var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "jobyhood") // events; e-mail-only sign-in in the pop-up
webView = WKWebView(frame: view.bounds, configuration: config)
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webView.uiDelegate = self
view.addSubview(webView)
var url = URLComponents(string: "https://jobyhood.com/embed/jobs")!
url.queryItems = [URLQueryItem(name: "key", value: "pk_your_public_key"),
URLQueryItem(name: "lang", value: "bg")]
webView.load(URLRequest(url: url.url!))
}
func userContentController(_ controller: WKUserContentController, didReceive message: WKScriptMessage) {
guard let text = message.body as? String,
let json = try? JSONSerialization.jsonObject(with: Data(text.utf8)) as? [String: Any],
let type = json["type"] as? String else { return }
if type == "jobyhood:needs-token" {
Task {
let token = try await yourBackend.jobyhoodToken() // see "Sign-in token"
let literal = String(data: try JSONEncoder().encode(token), encoding: .utf8)!
_ = try? await webView.evaluateJavaScript("JobyhoodJobs.setToken(\(literal))")
}
} else if type == "jobyhood:event", json["kind"] as? String == "matches_available",
let count = json["count"] as? Int {
showBadge(count)
}
}
// The pop-up opens in a web view of its own, so it can hand the session back to the embed.
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration,
for action: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
let popup = WKWebView(frame: view.bounds, configuration: configuration)
popup.uiDelegate = self
view.addSubview(popup)
return popup
}
func webViewDidClose(_ webView: WKWebView) { webView.removeFromSuperview() }
}- Дръжте web view на jobyhood.com: мостът е видим за всяка страница, която зареди. Връзките, които извеждат от вграждането, например Условията, отваряйте в системния браузър.
- Оставете JavaScript и DOM хранилището включени: вграждането пази сесията си в собственото си хранилище.
- Вносът на CV отваря избор на файл. На Android реализирайте
WebChromeClient.onShowFileChooser; WKWebView го показва сам. - Подавайте
lang, за да следва вграждането приложението ви, а не езика на телефона. - Ако показвате своя страница със скрипта, всичко работи като в уеб: нейният
onEventполучава събитията, а предаването им на приложението е работа на тази страница.
API без интерфейс
Изпращайте публичния си ключ при всяка заявка като X-Jobyhood-Partner-Key: pk_…. Адресът е https://api.jobyhood.com. Отговорите са JSON с ключове в camelCase, а заявки от браузър са разрешени от регистрираните ви адреси.
| Адрес | Предназначение |
|---|---|
GET /v1/partners/config/ | името, темата, настройките по подразбиране, разрешените адреси, consentVersion, текстовете за източника и адресите на Jobyhood |
GET /v1/jobs/ | обяви, с search, role, city, country, page и page_size; важат настройките ви по подразбиране, а извикващият може да ги промени; всеки ред носи partnerApplyUrl |
GET /v1/jobs/{id}/ | една обява |
POST /v1/partners/sso/ | вписва потребител (Токен за вход) |
POST /v1/partners/link/ | свързва вашата самоличност с влязъл потребител на Jobyhood (Свързване) |
POST /v1/users/jobs/express-interest/ | {job_posting_id}, с токена за достъп на потребителя |
POST /v1/auth/jwt/refresh/ | {refresh}: нов токен за достъп за потребителя |
POST /v1/partners/events/ | {kind: "impression" | "job_view", job_posting_id}, когато показвате обявите сами; вграждането брои своите |
GET /v1/partners/stats/ | дневни бройки, със сървърната тайна (Статистика) |
curl -H "X-Jobyhood-Partner-Key: pk_your_public_key" \
"https://api.jobyhood.com/v1/jobs/?city=Sofia&page_size=20&partner_key=pk_your_public_key"curl -X POST https://api.jobyhood.com/v1/partners/events/ \
-H "X-Jobyhood-Partner-Key: pk_your_public_key" -H "Content-Type: application/json" \
-d '{"kind": "job_view", "job_posting_id": 42}'Чат с ИИ за профила
Функциите с ИИ са на https://matcher.jobyhood.com, със същата заглавка за ключа и токена за достъп на потребителя.
| Адрес | Предназначение |
|---|---|
POST /v1/matcher/onboarding-chat/ | {message, conversation_history, extracted_data, language}: чатът, който попълва профила на нов потребител |
POST /v1/matcher/cv-chat/ | разговорът за внос на CV |
curl -X POST https://matcher.jobyhood.com/v1/matcher/onboarding-chat/ \
-H "X-Jobyhood-Partner-Key: pk_your_public_key" \
-H "Authorization: Bearer <the user’s access token>" -H "Content-Type: application/json" \
-d '{"message": "Hi", "conversation_history": [], "extracted_data": {}, "language": "bg"}'Ключът ви никога не замества токена на потребителя: без него отговорът е 401. Непознат или спрян ключ получава 403 Unknown partner key., а адрес на браузър, който не сте регистрирали, получава 403 Origin not allowed for this partner. Партньор без регистрирани адреси може да вика отвсякъде. Нищо не ви се таксува на извикване.
Статистика
GET https://api.jobyhood.com/v1/partners/stats/?from=2026-10-01&to=2026-10-21
Authorization: Partner <server secret>
200 {"range": {…}, "totals": {"impressions": 1200, "jobViews": 340, "signups": 25, "logins": 60, "interests": 18},
"daily": [{"date": "2026-10-01", "impressions": 40, "jobViews": 11, "signups": 1, "logins": 2, "interests": 0}, …]}Най-много 93 дни на заявка; по подразбиране последните 30 дни. Само бройки, никога лични данни. Партньорското табло показва същите числа.
Лимити и грешки
| Какво | Лимит |
|---|---|
| Заявки с вашия ключ | 600 на минута за ключ |
| Вход | 30 на минута за IP адрес на клиента |
| Събития | 120 на минута за ключ; показванията се броят веднъж на IP адрес на минута |
| Статистика | 60 на минута за IP адрес на клиента |
Над лимита API отговаря с 429: изчакайте и опитайте отново. Спрян или непознат ключ се отказва преди всеки адрес, с 403 {"detail": "Unknown partner key."}; спреният ключ нарочно изглежда като непознат. Отказите при вход носят code (таблицата).
Указване на източника
Където показвате обяви от Jobyhood, показвайте текстовете от config/.attribution: реда „С подкрепата на Jobyhood“ и, когато показана обява идва от публичния кариерен поток на работодател (feedProvider е попълнено), бележката за потока. Вграждането показва и двете само.
Какви данни се споделят
- Jobyhood е администратор на данните за профилите, създадени чрез вас.
- Получавате обобщени бройки и знанието, че вашите номера на потребители имат самоличност в Jobyhood.
- Никога не получавате съдържание на профили, CV, съобщения или кои обяви човек е разглеждал или за кои е кандидатствал.
- За съвпаденията получавате едно число,
countнаmatches_available. Самите съвпадения се показват само в рамката на Jobyhood.
Поддръжка
Въпроси, нов сайт или тестов ключ: пишете ни чрез формата на страницата за партньори.