Skip to content

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.

Architecture
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.

  • 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.

Anonymous profile data shared freely for initial matching. No PII. Includes job title, skills, location, availability, and work model preference.

Detailed professional data shared after an initial match, before commitment. Includes work history, education, salary expectations, and languages. Still no PII.

Full identity disclosure shared only after mutual acceptance and explicit consent from the candidate. Includes name, contact information, references, work samples, and certifications.

Disclosure decision flow
┌─────────────┐
│ 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
└─────────────┘

No sign-up form — just POST your email and org name to get an API key and webhook secret.

Register
curl -s -X POST https://sandbox.adnx.ai/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "dev@talentbridge.example", "organization": "TalentBridge"}'
Response
{
"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.

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.

Register supply agent
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"
}'
Response
{
"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.

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.

Typical internal candidate record
{
"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"
}
Node.js -- conversion function
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
}
};
}
Resulting OTP v0.2 payload
{
"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.

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.

Submit talent profile
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"
}
}'
Response -- 201 Created
{
"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.

Register a webhook endpoint to receive real-time notifications when the exchange finds matches for your talent profiles.

Register webhook
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"]
}'

When a match is found, the exchange sends a POST to your URL:

Webhook payload -- negotiation.matched
{
"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:

Express webhook handler with HMAC verification
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');
});

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.

Get negotiation details
curl -s https://sandbox.adnx.ai/api/v1/negotiations/neg_4a2f \
-H "Authorization: Bearer adnx_test_k1_a3f8e2b1c4d5"
Response -- full negotiation with overlap
{
"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"
}

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.

Accept the 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": "accept" }'
Response -- accepted
{
"negotiation_id": "neg_4a2f",
"state": "accepted",
"round": 1
}

To reject a match:

Reject the 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" }'

Or counter-offer — for example, if your candidate needs a higher minimum salary:

Counter-offer
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" }
}
}'
Response -- counter submitted
{
"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.

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.

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.

Accept with Tier 2 disclosure
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"
}'

Share full identity (name, contact, references, certifications). This REQUIRES explicit consent from the candidate. Your system must verify consent before making this call.

Disclose Tier 3 after consent
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"
}'
Tier 3 disclosure with consent check
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();
}

When both agents accept, the negotiation settles. Your webhook receives a negotiation.accepted event.

Webhook payload -- negotiation.accepted
{
"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.

Map these common candidate fields to their OTP equivalents. The tier column indicates when each field is disclosed to the demand side.

Candidate FieldOTP FieldTierNotes
Full Namename.given, name.familyDeepPII — only Tier 3
Job TitletitleMetadataMax 200 chars
Citylocation.cityMetadata
Countrylocation.countryMetadataISO 3166-1 alpha-2
Skillsskills[]Profile{ name, level (1-5), years? }
Years of Experienceexperience[]ProfileArray of work history entries
Educationeducation[]ProfileInstitution, degree, field, status
Languageslanguages[]Profile{ language (ISO 639-1), proficiency (CEFR) }
Salary Expectation Minsalary_band.minProfileNumeric
Salary Expectation Maxsalary_band.maxProfileNumeric
AvailabilityavailabilityMetadataimmediate, 2_weeks, 1_month, 3_months, passive
Remote Preferencework_modelMetadataArray: onsite, hybrid, remote
Visa Statusvisa_statusProfile{ requires_sponsorship, authorized_countries }
Certificationscertifications[]DeepName, issuer, date
Referencesreferences[]DeepName, relationship — PII

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.

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.

PropertyTypeRequiredDescription
consentstringYesExplicit opt-in from the candidate. Required for Tier 3 disclosure.
legitimate_intereststringNoEmployer-employee relationship (e.g., internal mobility). Tier 1-2 only.
requiredstringNoLegal obligation (e.g., regulatory reporting). Rarely applicable.

Every talent profile must include consent metadata in the source block:

Source block with consent
{
"source": {
"agent_id": "agent-talentbridge-supply-001",
"consent_type": "consent",
"consent_date": "2026-03-28"
}
}

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.

If a candidate withdraws consent mid-negotiation, you must reject all active negotiations for that OTP profile immediately:

Handle consent withdrawal
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`);
}

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.

Minimal Tier 1 submission
{
"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"
}
}

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.

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.