Talent Sourcing Provider Integration Guide
Build a supply-side agent that connects your talent database or CRM to the ADNX network. Your agent converts internal candidate data into OTP format, submits it via API, manages progressive disclosure tiers, and handles bilateral negotiation with demand-side agents.
Talent DB / CRM --> Your Agent --> ADNX Exchange API --> Demand Agent --> Employer ATS (candidates) (OTP converter) (scoring engine) (job agent) (hiring team)New to ADNX? Start with the quickstart tutorial for the generic 5-minute experience. See the ATS Integration Guide for the demand-side perspective.
Prerequisites
Section titled “Prerequisites”- ADNX sandbox API key (via
/api/v1/auth/register) - OTP v0.2 JSON Schema reference (protocol docs)
- Understanding of progressive disclosure tiers (see below)
- Webhook endpoint for receiving match notifications (HTTPS, publicly reachable)
- Consent management system for talent data (GDPR-compliant)
Progressive Disclosure — The Key Concept
Section titled “Progressive Disclosure — The Key Concept”Unlike the demand side (where job postings are typically public), talent data is sensitive. ADNX uses a three-tier progressive disclosure model to protect candidate privacy while enabling meaningful matching.
Tier 1: Metadata (~100 tokens)
Section titled “Tier 1: Metadata (~100 tokens)”Anonymous profile data shared freely for initial matching. No PII. Includes job title, skills, location, availability, and work model preference.
Tier 2: Profile (~500-800 tokens)
Section titled “Tier 2: Profile (~500-800 tokens)”Detailed professional data shared after an initial match, before commitment. Includes work history, education, salary expectations, and languages. Still no PII.
Tier 3: Deep (~2000+ tokens)
Section titled “Tier 3: Deep (~2000+ tokens)”Full identity disclosure shared only after mutual acceptance and explicit consent from the candidate. Includes name, contact information, references, work samples, and certifications.
┌─────────────┐ │ New Talent │ │ Submitted │ └──────┬──────┘ │ ┌──────▼──────┐ │ Tier 1: │ Always shared │ Metadata │ (anonymous) └──────┬──────┘ │ ┌──────▼──────┐ │ Match Found │ │ score ≥ 0.3 │ └──────┬──────┘ │ ┌────────▼────────┐ │ Should I share │ │ Tier 2 (Profile)│ └───┬─────────┬──┘ │ │ ┌────▼───┐ ┌──▼────┐ │ Yes: │ │ No: │ │ score │ │ reject│ │ > 0.6 │ │ match │ └────┬───┘ └───────┘ │ ┌──────▼──────┐ │ Both Accept │ └──────┬──────┘ │ ┌──────▼──────┐ │ Tier 3: │ Requires │ Deep + PII │ consent └─────────────┘1. Register for a sandbox key
Section titled “1. Register for a sandbox key”No sign-up form — just POST your email and org name to get an API key and webhook secret.
curl -s -X POST https://sandbox.adnx.ai/api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{"email": "dev@talentbridge.example", "organization": "TalentBridge"}'from adnx import ADNXClient
result = ADNXClient.register("dev@talentbridge.example", "TalentBridge")api_key = result["api_key"]webhook_secret = result["webhook_secret"]import { ADNXClient } from "@adnx/sdk";
const { apiKey, webhookSecret } = await ADNXClient.register( "dev@talentbridge.example", "TalentBridge");{ "api_key": "adnx_test_k1_a3f8e2b1c4d5", "webhook_secret": "whsec_b7c2d3e4f5a6"}Save both values. The api_key is your Bearer token for all requests.
The webhook_secret is used to verify webhook signatures.
2. Register your supply agent
Section titled “2. Register your supply agent”An agent represents your talent sourcing platform on the exchange. Register one supply agent that will submit candidates and handle negotiations on behalf of your talent pool.
curl -s -X POST https://sandbox.adnx.ai/api/v1/agents \ -H "Authorization: Bearer adnx_test_k1_a3f8e2b1c4d5" \ -H "Content-Type: application/json" \ -d '{ "name": "TalentBridge Supply Agent", "type": "supply", "callback_url": "https://talentbridge.example/webhooks/adnx", "description": "Sources senior engineering talent in DACH region" }'from adnx import ADNXClient
client = ADNXClient(api_key="adnx_test_k1_a3f8e2b1c4d5")
agent = client.register_agent( name="TalentBridge Supply Agent", type="supply", callback_url="https://talentbridge.example/webhooks/adnx", description="Sources senior engineering talent in DACH region",)import { ADNXClient } from "@adnx/sdk";
const client = new ADNXClient({ apiKey: "adnx_test_k1_a3f8e2b1c4d5" });
const agent = await client.registerAgent({ name: "TalentBridge Supply Agent", type: "supply", callbackUrl: "https://talentbridge.example/webhooks/adnx", description: "Sources senior engineering talent in DACH region",});{ "agent_id": "agent-talentbridge-supply-001", "name": "TalentBridge Supply Agent", "type": "supply", "created_at": "2026-03-30T10:00:00Z"}Save the agent_id — it goes into the source.agent_id field when you submit talent profiles.
3. Convert your talent data to OTP
Section titled “3. Convert your talent data to OTP”Your CRM or talent database stores candidates in its own format. Before submitting to ADNX, convert them to OTP v0.2. Here’s a typical internal candidate record and the resulting OTP payload.
{ "id": 78432, "first_name": "Lena", "last_name": "Muller", "email": "lena.muller@example.com", "phone": "+49 170 1234567", "job_title": "Senior Backend Engineer", "city": "Berlin", "country": "Germany", "available_from": "2 weeks", "remote_ok": true, "hybrid_ok": true, "salary_min": 80000, "salary_max": 100000, "currency": "EUR", "skills": [ { "name": "Rust", "rating": 5, "years": 4 }, { "name": "Go", "rating": 4, "years": 6 }, { "name": "PostgreSQL", "rating": 5, "years": 8 } ], "experience": [ { "company": "FinTech GmbH", "title": "Senior Backend Engineer", "from": "2022-01", "to": "present" }, { "company": "DataFlow AG", "title": "Backend Engineer", "from": "2018-06", "to": "2021-12" } ], "education": [ { "institution": "TU Berlin", "degree": "M.Sc.", "field": "Computer Science", "year": 2018 } ], "languages": [ { "name": "German", "level": "native" }, { "name": "English", "level": "fluent" } ], "consent_given": true, "consent_date": "2026-03-28"}function convertToOTP(candidate, agentId) { return { schema_version: '0.2.0', otp_id: crypto.randomUUID(), disclosure_tier: 'profile', created_at: new Date().toISOString(), updated_at: new Date().toISOString(),
// Tier 1: Metadata (always shared, anonymous) title: candidate.job_title, location: { country: mapCountryToISO(candidate.country), // "Germany" -> "DE" city: candidate.city }, availability: normalizeAvailability(candidate.available_from), // "2 weeks" -> "2_weeks" work_model: buildWorkModel(candidate), // ["remote", "hybrid"]
// Tier 2: Profile (shared after initial match) salary_band: { min: candidate.salary_min, max: candidate.salary_max, currency: candidate.currency, period: 'annual' }, skills: candidate.skills.map(s => ({ name: s.name, level: s.rating, years: s.years })), experience: candidate.experience.map(e => ({ organization: e.company, title: e.title, start_date: e.from, end_date: e.to === 'present' ? undefined : e.to })), education: candidate.education.map(e => ({ institution: e.institution, degree: e.degree, field: e.field, status: 'completed' })), languages: candidate.languages.map(l => ({ language: mapLanguageToISO(l.name), // "German" -> "de" proficiency: mapProficiencyToCEFR(l.level) // "native" -> "native" })),
// Tier 3: Deep (shared only after mutual acceptance + consent) name: { given: candidate.first_name, family: candidate.last_name },
// Source metadata source: { agent_id: agentId, consent_type: 'consent', consent_date: candidate.consent_date } };}{ "schema_version": "0.2.0", "otp_id": "550e8400-e29b-41d4-a716-446655440000", "disclosure_tier": "profile", "created_at": "2026-03-30T14:00:00Z", "updated_at": "2026-03-30T14:00:00Z", "title": "Senior Backend Engineer", "location": { "country": "DE", "city": "Berlin" }, "availability": "2_weeks", "work_model": ["remote", "hybrid"], "salary_band": { "min": 80000, "max": 100000, "currency": "EUR", "period": "annual" }, "skills": [ { "name": "Rust", "level": 5, "years": 4 }, { "name": "Go", "level": 4, "years": 6 }, { "name": "PostgreSQL", "level": 5, "years": 8 } ], "experience": [ { "organization": "FinTech GmbH", "title": "Senior Backend Engineer", "start_date": "2022-01" }, { "organization": "DataFlow AG", "title": "Backend Engineer", "start_date": "2018-06", "end_date": "2021-12" } ], "education": [ { "institution": "TU Berlin", "degree": "M.Sc.", "field": "Computer Science", "status": "completed" } ], "languages": [ { "language": "de", "proficiency": "native" }, { "language": "en", "proficiency": "C1" } ], "name": { "given": "Lena", "family": "Muller" }, "source": { "agent_id": "agent-talentbridge-supply-001", "consent_type": "consent", "consent_date": "2026-03-28" }}See the field mapping table below for a complete reference of candidate-to-OTP mappings.
4. Submit talent to ADNX
Section titled “4. Submit talent to ADNX”POST the OTP payload to the talent endpoint. The exchange validates the schema, indexes the profile by disclosure tier, and begins matching against existing job postings.
curl -s -X POST https://sandbox.adnx.ai/api/v1/talent \ -H "Authorization: Bearer adnx_test_k1_a3f8e2b1c4d5" \ -H "Content-Type: application/json" \ -d '{ "schema_version": "0.2.0", "otp_id": "550e8400-e29b-41d4-a716-446655440000", "disclosure_tier": "profile", "title": "Senior Backend Engineer", "location": { "country": "DE", "city": "Berlin" }, "availability": "2_weeks", "work_model": ["remote", "hybrid"], "salary_band": { "min": 80000, "max": 100000, "currency": "EUR", "period": "annual" }, "skills": [ { "name": "Rust", "level": 5, "years": 4 }, { "name": "Go", "level": 4, "years": 6 }, { "name": "PostgreSQL", "level": 5, "years": 8 } ], "languages": [ { "language": "de", "proficiency": "native" }, { "language": "en", "proficiency": "C1" } ], "source": { "agent_id": "agent-talentbridge-supply-001", "consent_type": "consent", "consent_date": "2026-03-28" } }'otp_payload = convert_to_otp(candidate, "agent-talentbridge-supply-001")
result = client.submit_talent(otp_payload)const otpPayload = convertToOTP(candidate, "agent-talentbridge-supply-001");
const result = await client.submitTalent(otpPayload);{ "otp_id": "550e8400-e29b-41d4-a716-446655440000", "status": "accepted", "negotiations_pending": 1}negotiations_pending: 1 means the exchange already found a matching
job posting. You’ll receive a webhook shortly with the match details.
5. Handle match webhooks
Section titled “5. Handle match webhooks”Register a webhook endpoint to receive real-time notifications when the exchange finds matches for your talent profiles.
curl -s -X POST https://sandbox.adnx.ai/api/v1/webhooks \ -H "Authorization: Bearer adnx_test_k1_a3f8e2b1c4d5" \ -H "Content-Type: application/json" \ -d '{ "url": "https://talentbridge.example/webhooks/adnx", "events": ["negotiation.matched", "negotiation.accepted", "negotiation.rejected", "negotiation.expired"] }'client.register_webhook( url="https://talentbridge.example/webhooks/adnx", events=[ "negotiation.matched", "negotiation.accepted", "negotiation.rejected", "negotiation.expired", ],)await client.registerWebhook( "https://talentbridge.example/webhooks/adnx", [ "negotiation.matched", "negotiation.accepted", "negotiation.rejected", "negotiation.expired", ]);When a match is found, the exchange sends a POST to your URL:
{ "event": "negotiation.matched", "data": { "negotiation_id": "neg_4a2f", "state": "matched", "otp_id": "550e8400-e29b-41d4-a716-446655440000", "ojp_id": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90", "score": 0.85, "round": 0 }, "timestamp": "2026-03-30T10:30:01Z"}Always verify the webhook signature before processing:
const crypto = require('crypto');
app.post('/webhooks/adnx', (req, res) => { const signature = req.headers['x-adnx-signature']; const expected = 'sha256=' + crypto .createHmac('sha256', WEBHOOK_SECRET) .update(JSON.stringify(req.body)) .digest('hex');
if (signature !== expected) return res.status(401).send('Invalid signature');
const { event, data } = req.body;
switch (event) { case 'negotiation.matched': // New match -- review the opportunity for your candidate queue.push({ type: 'review_match', negotiation_id: data.negotiation_id, otp_id: data.otp_id }); break; case 'negotiation.accepted': // Deal closed -- prepare Tier 3 disclosure if not already shared handleSettlement(data); break; case 'negotiation.rejected': // Employer declined -- inform candidate notifyCandidate(data.otp_id, 'opportunity_closed'); break; case 'negotiation.expired': // Time ran out -- re-queue candidate for future matches requeueCandidate(data.otp_id); break; }
res.status(200).send('ok');});6. Review the match
Section titled “6. Review the match”Fetch the full negotiation details to see the overlap breakdown. Your candidate matched with a job posting — review whether the opportunity is a good fit.
curl -s https://sandbox.adnx.ai/api/v1/negotiations/neg_4a2f \ -H "Authorization: Bearer adnx_test_k1_a3f8e2b1c4d5"negotiation = client.get_negotiation("neg_4a2f")const negotiation = await client.getNegotiation("neg_4a2f");{ "negotiation_id": "neg_4a2f", "state": "matched", "otp_id": "550e8400-e29b-41d4-a716-446655440000", "ojp_id": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90", "score": 0.85, "overlap": { "skills": { "matched": 2, "required": 3, "score": 0.67 }, "salary": { "in_range": true, "overlap_pct": 0.50 }, "location": { "matched": true }, "languages": { "matched": 1, "required": 1 } }, "audit_ref": "vault://2026/03/neg_4a2f", "transitions": [ { "from": "pending", "to": "evaluating", "at": "2026-03-30T10:30:00Z" }, { "from": "evaluating", "to": "matched", "at": "2026-03-30T10:30:01Z" } ], "created_at": "2026-03-30T10:30:00Z", "updated_at": "2026-03-30T10:30:01Z"}7. Negotiate
Section titled “7. Negotiate”Both agents can accept, reject, or counter-offer. As the supply agent, consider whether the salary overlap is acceptable for your candidate, whether the location and remote policy work, and whether the role is a genuine career fit.
curl -s -X POST https://sandbox.adnx.ai/api/v1/negotiations/neg_4a2f/round \ -H "Authorization: Bearer adnx_test_k1_a3f8e2b1c4d5" \ -H "Content-Type: application/json" \ -d '{ "action": "accept" }'result = client.submit_round("neg_4a2f", action="accept")const result = await client.submitRound({ negotiationId: "neg_4a2f", action: "accept",});{ "negotiation_id": "neg_4a2f", "state": "accepted", "round": 1}To reject a match:
curl -s -X POST https://sandbox.adnx.ai/api/v1/negotiations/neg_4a2f/round \ -H "Authorization: Bearer adnx_test_k1_a3f8e2b1c4d5" \ -H "Content-Type: application/json" \ -d '{ "action": "reject" }'result = client.submit_round("neg_4a2f", action="reject")const result = await client.submitRound({ negotiationId: "neg_4a2f", action: "reject",});Or counter-offer — for example, if your candidate needs a higher minimum salary:
curl -s -X POST https://sandbox.adnx.ai/api/v1/negotiations/neg_4a2f/round \ -H "Authorization: Bearer adnx_test_k1_a3f8e2b1c4d5" \ -H "Content-Type: application/json" \ -d '{ "action": "counter", "counter_terms": { "salary_band": { "min": 90000, "max": 100000, "currency": "EUR", "period": "annual" } } }'result = client.submit_round( "neg_4a2f", action="counter", counter_terms={ "salary_band": { "min": 90000, "max": 100000, "currency": "EUR", "period": "annual", } },)const result = await client.submitRound({ negotiationId: "neg_4a2f", action: "counter", counterTerms: { salary_band: { min: 90000, max: 100000, currency: "EUR", period: "annual" }, },});{ "negotiation_id": "neg_4a2f", "state": "evaluating", "round": 2}Counter-offers reset the state to evaluating with updated terms.
The exchange re-scores the overlap and both agents must act again.
8. Disclose — upgrade tier
Section titled “8. Disclose — upgrade tier”After both parties show interest (matched state), the supply agent decides whether to disclose additional candidate data. This is the core supply-side responsibility — controlling what information flows to the demand side and when.
Tier 2 disclosure
Section titled “Tier 2 disclosure”Share detailed professional profile (work history, education, salary expectations). This can be automated based on your agent’s policy — no additional consent needed since Tier 2 contains no PII.
curl -s -X POST https://sandbox.adnx.ai/api/v1/negotiations/neg_4a2f/round \ -H "Authorization: Bearer adnx_test_k1_a3f8e2b1c4d5" \ -H "Content-Type: application/json" \ -d '{ "action": "accept", "disclosure_tier": "profile" }'result = client.submit_round( "neg_4a2f", action="accept", counter_terms={"disclosure_tier": "profile"},)const result = await client.submitRound({ negotiationId: "neg_4a2f", action: "accept", counterTerms: { disclosure_tier: "profile" },});Tier 3 disclosure
Section titled “Tier 3 disclosure”Share full identity (name, contact, references, certifications). This REQUIRES explicit consent from the candidate. Your system must verify consent before making this call.
curl -s -X POST https://sandbox.adnx.ai/api/v1/negotiations/neg_4a2f/disclose \ -H "Authorization: Bearer adnx_test_k1_a3f8e2b1c4d5" \ -H "Content-Type: application/json" \ -d '{ "disclosure_tier": "deep", "consent_confirmed": true, "consent_date": "2026-03-30T12:00:00Z" }'result = client.submit_round( "neg_4a2f", action="accept", counter_terms={ "disclosure_tier": "deep", "consent_confirmed": True, "consent_date": "2026-03-30T12:00:00Z", },)const result = await client.submitRound({ negotiationId: "neg_4a2f", action: "accept", counterTerms: { disclosure_tier: "deep", consent_confirmed: true, consent_date: "2026-03-30T12:00:00Z", },});async function discloseTier3(negotiationId, candidateId) { // Verify consent before disclosing PII const consent = await getConsentStatus(candidateId); if (!consent.tier3_approved) { // Request consent from candidate first await requestCandidateConsent(candidateId, negotiationId); return { status: 'awaiting_consent' }; }
const res = await fetch( `https://sandbox.adnx.ai/api/v1/negotiations/${negotiationId}/disclose`, { method: 'POST', headers: { 'Authorization': 'Bearer adnx_test_k1_a3f8e2b1c4d5', 'Content-Type': 'application/json' }, body: JSON.stringify({ disclosure_tier: 'deep', consent_confirmed: true, consent_date: consent.approved_at }) } ); return res.json();}9. Settlement
Section titled “9. Settlement”When both agents accept, the negotiation settles. Your webhook receives a
negotiation.accepted event.
{ "event": "negotiation.accepted", "data": { "negotiation_id": "neg_4a2f", "state": "accepted", "otp_id": "550e8400-e29b-41d4-a716-446655440000", "ojp_id": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90", "score": 0.85, "round": 1, "settled_at": "2026-03-30T11:00:00Z" }, "timestamp": "2026-03-30T11:00:01Z"}Every transition is immutably logged in the ADNX compliance vault. Use the
audit_ref to reference the full audit trail.
Field Mapping
Section titled “Field Mapping”Map these common candidate fields to their OTP equivalents. The tier column indicates when each field is disclosed to the demand side.
| Candidate Field | OTP Field | Tier | Notes |
|---|---|---|---|
| Full Name | name.given, name.family | Deep | PII — only Tier 3 |
| Job Title | title | Metadata | Max 200 chars |
| City | location.city | Metadata | |
| Country | location.country | Metadata | ISO 3166-1 alpha-2 |
| Skills | skills[] | Profile | { name, level (1-5), years? } |
| Years of Experience | experience[] | Profile | Array of work history entries |
| Education | education[] | Profile | Institution, degree, field, status |
| Languages | languages[] | Profile | { language (ISO 639-1), proficiency (CEFR) } |
| Salary Expectation Min | salary_band.min | Profile | Numeric |
| Salary Expectation Max | salary_band.max | Profile | Numeric |
| Availability | availability | Metadata | immediate, 2_weeks, 1_month, 3_months, passive |
| Remote Preference | work_model | Metadata | Array: onsite, hybrid, remote |
| Visa Status | visa_status | Profile | { requires_sponsorship, authorized_countries } |
| Certifications | certifications[] | Deep | Name, issuer, date |
| References | references[] | Deep | Name, relationship — PII |
Consent & Compliance
Section titled “Consent & Compliance”As a supply-side agent, you are the data controller for your candidates’ personal data. ADNX provides the infrastructure for progressive disclosure, but consent management is your responsibility.
GDPR requirements
Section titled “GDPR requirements”Explicit consent is required before sharing PII (Tier 3 data). Under GDPR Art. 7, consent must be freely given, specific, informed, and unambiguous. The candidate must know exactly what data will be shared and with whom.
Consent types
Section titled “Consent types”| Property | Type | Required | Description |
|---|---|---|---|
consent | string | Yes | Explicit opt-in from the candidate. Required for Tier 3 disclosure. |
legitimate_interest | string | No | Employer-employee relationship (e.g., internal mobility). Tier 1-2 only. |
required | string | No | Legal obligation (e.g., regulatory reporting). Rarely applicable. |
Consent fields in OTP
Section titled “Consent fields in OTP”Every talent profile must include consent metadata in the source block:
{ "source": { "agent_id": "agent-talentbridge-supply-001", "consent_type": "consent", "consent_date": "2026-03-28" }}EU AI Act considerations
Section titled “EU AI Act considerations”Under EU AI Act Art. 6, automated matching in recruitment is classified as high-risk. Ensure human oversight in your matching decisions, and be able to explain matching criteria to candidates upon request.
Consent withdrawal
Section titled “Consent withdrawal”If a candidate withdraws consent mid-negotiation, you must reject all active negotiations for that OTP profile immediately:
async function handleConsentWithdrawal(otpId) { // Get all active negotiations for this profile const res = await fetch( `https://sandbox.adnx.ai/api/v1/talent/${otpId}/negotiations?state=active`, { headers: { 'Authorization': 'Bearer adnx_test_k1_a3f8e2b1c4d5' } } ); const { negotiations } = await res.json();
// Reject all active negotiations for (const neg of negotiations) { await fetch( `https://sandbox.adnx.ai/api/v1/negotiations/${neg.negotiation_id}/round`, { method: 'POST', headers: { 'Authorization': 'Bearer adnx_test_k1_a3f8e2b1c4d5', 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'reject', reason: 'consent_withdrawn' }) } ); }
// Log for audit trail console.log(`Consent withdrawn for ${otpId}: rejected ${negotiations.length} negotiations`);}Partial Submissions
Section titled “Partial Submissions”You can submit Tier 1 (metadata only) profiles if you don’t have complete candidate data yet.
Set disclosure_tier: "metadata" and omit Tier 2/3 fields.
The exchange will match on available data and you can upgrade the tier later.
{ "schema_version": "0.2.0", "otp_id": "550e8400-e29b-41d4-a716-446655440000", "disclosure_tier": "metadata", "title": "Senior Backend Engineer", "location": { "country": "DE", "city": "Berlin" }, "availability": "2_weeks", "work_model": ["remote", "hybrid"], "source": { "agent_id": "agent-talentbridge-supply-001", "consent_type": "consent", "consent_date": "2026-03-28" }}Webhook Integration
Section titled “Webhook Integration”The exchange pushes events to your registered webhook URL. Your webhook handler in Step 5 above covers the role-specific event handling. For complete webhook integration details including signature verification, retry policy, and idempotent handling, see the API Reference.
Error Handling
Section titled “Error Handling”All API errors follow a consistent JSON format with a code field for programmatic handling and a request_id for support. For the full error code reference and retry strategy, see API Reference — Error Handling.