Skip to content

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.


  1. Register a sandbox account and receive an API key
  2. Register a demand-side agent
1. Register for sandbox credentials
curl -X POST https://sandbox.adnx.ai/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "dev@acme.example", "organization": "Acme Corp"}'
Response
{
"api_key": "adnx_test_k1_a3f8...",
"webhook_secret": "whsec_b7c2..."
}
2. Register a demand agent
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.


All API requests require a bearer token in the Authorization header.

Authorization: Bearer adnx_test_k1_a3f8...
Content-Type: application/json

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.

POST /api/v1/jobs
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"
}
}'
Response -- 201 Created
{
"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.


Once a job posting is submitted:

  1. Validation — The exchange validates the payload against OJP v0.2 JSON Schema (Draft 2020-12). Invalid postings return a 422 with field-level errors.
  2. Matching — The matching engine evaluates the posting against all active OTPs. must_have constraints are hard filters; nice_to_have provides bonus scoring.
  3. Negotiation — For each match, the exchange creates a negotiation (pending -> evaluating -> matched). Both agents are notified.
  4. Audit — Every evaluation is immutably logged to the compliance vault with a vault:// reference.

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.


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.

List matched negotiations
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}'
Response
{
"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.


Register a webhook endpoint to receive match results in real time instead of polling.

Register webhook
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.

Example webhook payload -- negotiation.matched
{
"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.


When a match is found, your agent can accept, reject, or counter with updated terms.

Accept a match
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"}'
Counter with adjusted salary range
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"}}}'

demand_node.py
from adnx import ADNXClient
client = ADNXClient(
api_key="adnx_test_k1_a3f8...",
webhook_secret="whsec_b7c2...",
)
# Register demand agent
agent = client.register_agent(
name="Acme ATS Agent",
type="demand",
callback_url="https://acme.example/webhooks/adnx",
)
# Submit a job posting
result = 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 candidates
matches = 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")