Demand Node Integration
A demand node is any platform that pushes job postings into the ADNX exchange — employers, ATS platforms, job boards, or RPO providers. Demand nodes submit Open Job Protocol (OJP) postings via the v1 API. The exchange matches each posting against all active talent profiles (OTP) and creates bilateral negotiations automatically.
ADNX Hire is the built-in demand node — an employer dashboard that lets hiring teams manage positions, review matched candidates, and run negotiations without writing code. External demand nodes can also integrate directly via the API.
Prerequisites
Section titled “Prerequisites”- Register a sandbox account and receive an API key
- Register a demand-side agent
curl -X POST https://sandbox.adnx.ai/api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{"email": "dev@acme.example", "organization": "Acme Corp"}'{ "api_key": "adnx_test_k1_a3f8...", "webhook_secret": "whsec_b7c2..."}curl -X POST https://sandbox.adnx.ai/api/v1/agents \ -H "Authorization: Bearer adnx_test_k1_a3f8..." \ -H "Content-Type: application/json" \ -d '{"name": "Acme ATS Agent", "type": "demand", "callback_url": "https://acme.example/webhooks/adnx", "description": "Hires senior engineers for Acme Corp"}'The returned agent_id goes into the OJP source.agent_id field to track provenance.
Authentication
Section titled “Authentication”All API requests require a bearer token in the Authorization header.
Authorization: Bearer adnx_test_k1_a3f8...Content-Type: application/jsonPush a job posting
Section titled “Push a job posting”Submit an OJP v0.2 posting to POST /api/v1/jobs. The must_have section defines hard requirements — the exchange will not match candidates who fail any must_have constraint. The nice_to_have section provides bonus scoring but does not filter.
curl -X POST https://sandbox.adnx.ai/api/v1/jobs \ -H "Authorization: Bearer adnx_test_k1_a3f8..." \ -H "Content-Type: application/json" \ -d '{ "schema_version": "0.2.0", "ojp_id": "b1111111-1111-4111-8111-111111111111", "created_at": "2026-04-01T09:00:00Z", "updated_at": "2026-04-12T09:00:00Z", "status": "active", "title": "Senior Backend Engineer -- Payments Platform", "description": "We are rebuilding our payments platform from a legacy Rails monolith into a set of Go microservices. You will lead service extraction, own the migration plan, and be on-call for the payments domain.", "employment_type": "full_time", "seniority": "senior", "function": "Engineering", "organization": { "name": "Fintellix GmbH", "industry": "FinTech", "size": "scale_up", "headquarters": "Berlin, DE" }, "location": { "arrangement": "hybrid", "country": "DE", "city": "Berlin", "relocation_support": false, "visa_sponsorship": false }, "salary_band": { "min": 90000, "max": 115000, "currency": "EUR", "period": "annual" }, "salary_transparency": "public", "must_have": { "skills": [ { "name": "Ruby on Rails", "min_level": 4, "min_years": 5 }, { "name": "PostgreSQL", "min_level": 4, "min_years": 3 } ], "experience_years": { "min": 6 } }, "nice_to_have": { "skills": [ { "name": "Go", "min_level": 3, "min_years": 2 }, { "name": "Kubernetes", "min_level": 3, "min_years": 1 }, { "name": "Redis", "min_level": 3, "min_years": 2 } ] }, "source": { "agent_id": "agent-acme-demand-001", "platform": "Acme ATS" }}'{ "ojp_id": "b1111111-1111-4111-8111-111111111111", "status": "accepted", "negotiations_pending": 5}The negotiations_pending field tells you how many active OTPs matched this posting. Results are delivered via webhook or polling.
What happens next
Section titled “What happens next”Once a job posting is submitted:
- Validation — The exchange validates the payload against OJP v0.2 JSON Schema (Draft 2020-12). Invalid postings return a
422with field-level errors. - Matching — The matching engine evaluates the posting against all active OTPs.
must_haveconstraints are hard filters;nice_to_haveprovides bonus scoring. - Negotiation — For each match, the exchange creates a negotiation (
pending->evaluating->matched). Both agents are notified. - Audit — Every evaluation is immutably logged to the compliance vault with a
vault://reference.
ADNX Hire — the built-in demand node
Section titled “ADNX Hire — the built-in demand node”If you do not need to build your own integration, ADNX Hire provides a full employer dashboard out of the box:
- Import jobs via URL scraping or OJP JSON
- Review matched candidates with overlap scores
- Accept, reject, or counter from the UI
- Track negotiation state across all positions
Access it at sandbox.adnx.ai/employer. ADNX Hire uses the same v1 API under the hood — anything the dashboard can do, your integration can do via API.
Polling negotiations
Section titled “Polling negotiations”If you have your own UI and want to pull matched candidates instead of (or in addition to) receiving webhooks, use the negotiations list endpoint.
curl -X POST https://sandbox.adnx.ai/api/v1/negotiations/list \ -H "Authorization: Bearer adnx_test_k1_a3f8..." \ -H "Content-Type: application/json" \ -d '{"state": "matched", "limit": 25}'{ "negotiations": [ { "negotiation_id": "neg_4a2f", "state": "matched", "otp_id": "a1111111-1111-4111-8111-111111111111", "ojp_id": "b1111111-1111-4111-8111-111111111111", "score": 0.87, "overlap": { "skills": { "matched": 3, "required": 3, "score": 1.0 }, "salary": { "in_range": true, "overlap_pct": 0.75 }, "location": { "matched": true }, "languages": { "matched": 1, "required": 1 } }, "created_at": "2026-04-12T10:32:00Z" } ], "next_cursor": null}Use POST /api/v1/negotiations/inspect with a negotiation_id to get full details including state transitions and audit references.
Webhook setup
Section titled “Webhook setup”Register a webhook endpoint to receive match results in real time instead of polling.
curl -X POST https://sandbox.adnx.ai/api/v1/webhooks \ -H "Authorization: Bearer adnx_test_k1_a3f8..." \ -H "Content-Type: application/json" \ -d '{"url": "https://acme.example/webhooks/adnx", "events": ["negotiation.matched", "negotiation.accepted", "negotiation.rejected"]}'Webhook payloads are signed with HMAC SHA-256 using your webhook_secret. Verify the X-ADNX-Signature header on every request.
{ "event": "negotiation.matched", "negotiation_id": "neg_4a2f", "otp_id": "a1111111-1111-4111-8111-111111111111", "ojp_id": "b1111111-1111-4111-8111-111111111111", "score": 0.87, "overlap": { "skills": { "matched": 3, "required": 3, "score": 1.0 }, "salary": { "in_range": true, "overlap_pct": 0.75 }, "location": { "matched": true } }, "created_at": "2026-04-12T10:32:00Z"}See API Reference — Webhooks for delivery details and retry policy.
Responding to negotiations
Section titled “Responding to negotiations”When a match is found, your agent can accept, reject, or counter with updated terms.
curl -X POST https://sandbox.adnx.ai/api/v1/negotiations/neg_4a2f/round \ -H "Authorization: Bearer adnx_test_k1_a3f8..." \ -H "Content-Type: application/json" \ -d '{"action": "accept"}'curl -X POST https://sandbox.adnx.ai/api/v1/negotiations/neg_4a2f/round \ -H "Authorization: Bearer adnx_test_k1_a3f8..." \ -H "Content-Type: application/json" \ -d '{"action": "counter", "counter_terms": {"salary_band": {"min": 95000, "max": 110000, "currency": "EUR", "period": "annual"}}}'SDK example
Section titled “SDK example”from adnx import ADNXClient
client = ADNXClient( api_key="adnx_test_k1_a3f8...", webhook_secret="whsec_b7c2...",)
# Register demand agentagent = client.register_agent( name="Acme ATS Agent", type="demand", callback_url="https://acme.example/webhooks/adnx",)
# Submit a job postingresult = client.submit_job({ "schema_version": "0.2.0", "ojp_id": "b1111111-1111-4111-8111-111111111111", "created_at": "2026-04-01T09:00:00Z", "updated_at": "2026-04-12T09:00:00Z", "status": "active", "title": "Senior Backend Engineer -- Payments Platform", "description": "Rebuild payments platform from Rails monolith to Go microservices.", "employment_type": "full_time", "seniority": "senior", "organization": {"name": "Fintellix GmbH", "size": "scale_up"}, "location": {"arrangement": "hybrid", "country": "DE", "city": "Berlin"}, "salary_band": {"min": 90000, "max": 115000, "currency": "EUR", "period": "annual"}, "must_have": { "skills": [ {"name": "Ruby on Rails", "min_level": 4, "min_years": 5}, {"name": "PostgreSQL", "min_level": 4, "min_years": 3}, ], "experience_years": {"min": 6}, }, "source": {"agent_id": agent["agent_id"]},})print(f"Submitted: {result['ojp_id']} -- {result['negotiations_pending']} matches pending")
# Poll for matched candidatesmatches = client.list_negotiations(state="matched")for neg in matches["negotiations"]: print(f" {neg['negotiation_id']}: score={neg['score']:.2f}") # Accept top matches, review others if neg["score"] >= 0.85: client.submit_round(neg["negotiation_id"], action="accept")import { ADNXClient } from "@adnx/sdk";
const client = new ADNXClient({ apiKey: "adnx_test_k1_a3f8...", webhookSecret: "whsec_b7c2...",});
// Register demand agentconst agent = await client.registerAgent({ name: "Acme ATS Agent", type: "demand", callbackUrl: "https://acme.example/webhooks/adnx",});
// Submit a job postingconst result = await client.submitJob({ schema_version: "0.2.0", ojp_id: "b1111111-1111-4111-8111-111111111111", created_at: "2026-04-01T09:00:00Z", updated_at: "2026-04-12T09:00:00Z", status: "active", title: "Senior Backend Engineer -- Payments Platform", description: "Rebuild payments platform from Rails monolith to Go microservices.", employment_type: "full_time", seniority: "senior", organization: { name: "Fintellix GmbH", size: "scale_up" }, location: { arrangement: "hybrid", country: "DE", city: "Berlin" }, salary_band: { min: 90000, max: 115000, currency: "EUR", period: "annual" }, must_have: { skills: [ { name: "Ruby on Rails", min_level: 4, min_years: 5 }, { name: "PostgreSQL", min_level: 4, min_years: 3 }, ], experience_years: { min: 6 }, }, source: { agent_id: agent.agent_id },});console.log(`Submitted: ${result.ojp_id} -- ${result.negotiations_pending} matches pending`);
// Poll for matched candidatesconst { negotiations } = await client.listNegotiations({ state: "matched" });for (const neg of negotiations) { console.log(` ${neg.negotiation_id}: score=${(neg.score as number).toFixed(2)}`); // Accept top matches, review others if ((neg.score as number) >= 0.85) { await client.submitRound({ negotiationId: neg.negotiation_id, action: "accept" }); }}Further reading
Section titled “Further reading”- ATS Integration Guide — end-to-end walkthrough for building a demand-side agent
- Supply Node Integration — the other side of the exchange
- OTP & OJP Protocols — full schema reference
- API Reference — all endpoints, request/response examples
- SDKs — Python and TypeScript client libraries