Документация за партньори

Интегрирайте обявите на Jobyhood

Всичко, от което един инженер има нужда, за да покаже обявите на Jobyhood в сайта или приложението ви: вграждането, входа, мобилните web view изгледи и API без интерфейс.

Преди да започнете

Има два начина за интеграция. Те ползват един партньорски запис, един публичен ключ, един токен за вход и едни и същи лимити.

  • Вграждане: един таг script или същият адрес в web view на приложение. Нашите компоненти с вашия акцентен цвят и език. Входът, съгласието, попълването на профила и „Проявявам интерес“ стават в рамката.
  • API без интерфейс: JSON адреси, които извиквате с ключа си, а интерфейса правите вие. Достъпни са обявите, входът, проявата на интерес и чатът с ИИ за профила.
Данни за достъпКъде стоятЗа какво служат
Публичен ключ pk_…в страниците и приложенията ви; вижда се в кода на страницатаидентифицира ви: настройки, указване на източника, обхват и лимити
Сървърна тайна (64 шестнадесетични знака)само на вашия сървърподписва токените за вход и чете статистиката; след смяна старата работи още 10 минути

Jobyhood записва и разрешените ви адреси (https://news.example), които решават кой може да вгражда рамката и да вика API от браузър; държава по подразбиране и филтри по желание; темата ви; дали входът е включен; и дали Jobyhood се доверява на проверката ви на имейла, без която не се създават акаунти.

Поискайте партньорски запис чрез формата на страницата за партньори. След това екипът ви управлява ключове, сайтове, тема и членове в партньорското табло.

Вграждане

Поставете скрипта там, където искате обявите. Той вмъква iframe на това място и поддържа височината му според съдържанието.

HTML
Скрипт с предаване на токен за вход и брояч на съвпаденията
<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-langbg, enпо подразбиране: езикът на браузъра на посетителя
data-themelight, dark, autoauto следва системната настройка на посетителя
data-accent#rrggbbбутони и акценти
data-radius0–32заобляне на ъглите в пиксели
data-countryдвубуквен код, например BGдържавата на обявите
data-filtersq=…&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} само от тази страница и само от някой от регистрираните ви адреси.

JavaScript
Слушане на рамката и отговор към нея
// <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_verifiedtrue; нужно е за създаване на акаунт и Jobyhood трябва да се доверява на проверката ви
given_name, family_name, phone, localeпо желание
JavaScript
С Node (jsonwebtoken)
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 });
});
Python
С Python (PyJWT)
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= в адреса важи само във вашата рамка или в приложение с мост, никога в обикновен раздел на браузъра. Без интерфейс го обменяте сами: първо покажете екрана за съгласие и изпратете версията му.

HTTP
Вписване на потребител
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, expired400създайте нов токен
replayed409създайте нов токен; всеки jti работи веднъж
consent_required400покажете екрана за съгласие с текущата consentVersion и опитайте отново
email_not_verified403имейлът не е потвърден или Jobyhood още не се доверява на проверката ви: предложете регистрация в jobyhood.com
email_in_use409имейлът вече има акаунт в Jobyhood: свържете го; токенът не се изразходва
email_mismatch400само при link/: имейлът на влезлия потребител се различава от този в токена
account_linked_elsewhere409този sub вече е свързан с друг профил в Jobyhood
company_account, account_unavailable403link/ и повторен вход: акаунтът е фирмен или блокиран (sso/ отговаря email_in_use за всеки съществуващ акаунт, включително фирмен)
sso_disabled403свържете се с Jobyhood
idp_unavailable502само при 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.

HTTP
Свързване без интерфейс
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, а вграждането го прочита веднъж и го маха от адреса. Токен в заявката (след „?“) се пренебрегва.

URL
Адресът на вграждането; токенът минава през setToken
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 е самата страница. Без мост вграждането не изпраща нищо, а „Продължи с …“ работи само с токен от адреса.

Kotlin
За Android (WebView)
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()
        }
    }
}
Swift
За iOS (WKWebView)
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/дневни бройки, със сървърната тайна (Статистика)
Shell
Списък с обяви
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"
Shell
Отчитане на преглед на обява
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
Shell
Една реплика в чата за профила
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. Партньор без регистрирани адреси може да вика отвсякъде. Нищо не ви се таксува на извикване.

Статистика

HTTP
Дневни бройки
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.

Поддръжка

Въпроси, нов сайт или тестов ключ: пишете ни чрез формата на страницата за партньори.