DecisionCore exposes a standard REST API and an MCP server. Any system that can make an HTTP POST request can integrate. This guide covers authentication, both API endpoints, Python and curl examples, and the MCP integration pattern.
Get a free API key, send a test POST, verify the response. The full pipeline runs in seconds.
# Register at /portal and copy your API key. # Keys are prefixed with "dc-". export DC_API_KEY="dc-your-key-here"
curl -s -X POST https://decisioncore.emas.ai/explain \ -H "Authorization: Bearer $DC_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "decision": "Loan application rejected", "subject_description": "Applicant DSR 62%, policy ceiling 60%", "sector": "financial", "tenant_id": "my-org" }'
{
"audit_id": "ADMT-EXP-8a2f1c3d",
"decision": "Loan application rejected",
"explanation": "The loan application was rejected because the applicant's
Debt Service Ratio (DSR) of 62% exceeds the institution's affordability
limit of 60% (Credit Policy §3.1). The applicant may ask for a
human review of this automated decision...",
"regulations_cited": [
"Credit Policy §3.1 — Affordability limit (DSR)",
"Credit Policy §5 — Human review on request"
],
"reasoning_chain": "THOUGHT: DSR 62% exceeds the 60% limit. Matched Credit Policy §3.1...",
"report_url": "/explain/report/ADMT-EXP-8a2f1c3d",
"results_used": 1,
"results_remaining": 2
}
All API requests require a Bearer token in the Authorization header. Tokens are issued at registration and never expire — rotate them from the dashboard.
Authorization: Bearer dc-your-api-key-here
| Key prefix | Scope | Deduction |
|---|---|---|
| dc- | Full API access — /explain, /explain/report | 1 result per /explain call |
Generates a JSON explanation record. Returns the explanation text, regulatory citations, DeepSeek R1 reasoning chain, and a URL for the PDF report. Deducts 1 result (RM5 at Starter).
| Field | Type | Required | Description |
|---|---|---|---|
| decision | string | Yes | The decision outcome to explain. E.g. "Loan rejected", "Application shortlisted", "Academic probation". |
| subject_description | string | Yes | Context about the subject and the factors that drove the decision. E.g. "DSR 62%, institution limit 60%". Max 2,000 chars. |
| sector | string | No | Hint for regulatory corpus selection. One of: financial, hr, academic. Defaults to general corpus. |
| tenant_id | string | No | Your organisation identifier — scopes the audit log. Defaults to your account slug. |
| language | string | No | Output language for the explanation text. en (default) or ms (Bahasa Malaysia). |
{
"audit_id": "ADMT-EXP-{uuid}", // unique record ID
"decision": "string", // echoed from request
"explanation": "string", // plain-language explanation
"regulations_cited": ["string", ...], // matched provisions
"reasoning_chain": "string", // DeepSeek R1 thought trace
"report_url": "/explain/report/{id}", // PDF download URL
"results_used": 1,
"results_remaining": integer
}
Generates and returns a formal A4 PDF explanation report. Same request body as /explain. Returns application/pdf. Does NOT deduct an additional result — the PDF is included in the /explain call cost.
curl -s -X POST https://decisioncore.emas.ai/explain/report \ -H "Authorization: Bearer $DC_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "decision": "Loan application rejected", "subject_description": "Applicant DSR 62%, policy ceiling 60%", "sector": "financial", "tenant_id": "my-org" }' \ --output dc-explanation.pdf
You can also retrieve a previously generated PDF by Audit ID using the URL returned in the /explain response:
curl -s https://decisioncore.emas.ai/explain/report/ADMT-EXP-8a2f1c3d \
-H "Authorization: Bearer $DC_API_KEY" \
--output ADMT-EXP-8a2f1c3d.pdf
Using the standard requests library. Compatible with any Python 3.8+ environment.
import requests import os DC_API_KEY = os.environ["DC_API_KEY"] BASE_URL = "https://decisioncore.emas.ai" HEADERS = { "Authorization": f"Bearer {DC_API_KEY}", "Content-Type": "application/json", } def explain(decision: str, subject_description: str, sector: str = "financial", tenant_id: str = "my-org") -> dict: r = requests.post( f"{BASE_URL}/explain", json={ "decision": decision, "subject_description": subject_description, "sector": sector, "tenant_id": tenant_id, }, headers=HEADERS, timeout=60, ) r.raise_for_status() return r.json() def download_report(audit_id: str, output_path: str) -> None: r = requests.get( f"{BASE_URL}/explain/report/{audit_id}", headers=HEADERS, timeout=60, ) r.raise_for_status() with open(output_path, "wb") as f: f.write(r.content) # Example: loan rejection result = explain( decision="Loan application rejected", subject_description="DSR 62%, institution limit 60%", sector="financial", ) print(f"Audit ID: {result['audit_id']}") print(f"Explanation: {result['explanation'][:200]}...") print(f"Regulations: {result['regulations_cited']}") # Download the formal PDF download_report(result["audit_id"], "dc-explanation.pdf") print("PDF saved to dc-explanation.pdf")
Using the native fetch API (Node 18+). No additional packages required.
const fs = require('fs'); const DC_API_KEY = process.env.DC_API_KEY; const BASE_URL = 'https://decisioncore.emas.ai'; async function explain(decision, subjectDescription, sector = 'financial') { const res = await fetch(`${BASE_URL}/explain`, { method: 'POST', headers: { 'Authorization': `Bearer ${DC_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ decision, subject_description: subjectDescription, sector }), }); if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`); return res.json(); } async function downloadReport(auditId, outputPath) { const res = await fetch(`${BASE_URL}/explain/report/${auditId}`, { headers: { 'Authorization': `Bearer ${DC_API_KEY}` }, }); if (!res.ok) throw new Error(`Report error ${res.status}`); const buffer = Buffer.from(await res.arrayBuffer()); fs.writeFileSync(outputPath, buffer); } // Example usage explain('Candidate rejected', 'ATS score 58/100, threshold 65', 'hr') .then(async result => { console.log('Audit ID:', result.audit_id); console.log('Regulations:', result.regulations_cited); await downloadReport(result.audit_id, 'dc-explanation.pdf'); console.log('PDF saved.'); }) .catch(console.error);
DecisionCore ships as an MCP server — natively callable from Claude and any MCP-compatible client. Three tools available: request_explanation, check_consent, log_decision.
{
"mcpServers": {
"decisioncore": {
"command": "/path/to/venv/bin/python3",
"args": ["/path/to/decisioncore/mcp_server.py"],
"env": {
"DC_API_KEY": "dc-your-key-here",
"DC_BASE_URL": "https://decisioncore.emas.ai"
}
}
}
}
| MCP Tool | Inputs | Description |
|---|---|---|
| request_explanation | decision, subject_description, sector | Generates a JSON explanation record and returns the explanation text + Audit ID. Deducts 1 result. |
| check_consent | user_id, purpose_id, tenant_id | Returns the current consent status for a given user + purpose combination. |
| log_decision | decision, subject_id, outcome, tenant_id | Writes a decision record to the Sovereign Audit Log without generating an explanation. |
Why not just ask an LLM to explain the decision directly?
| Concern | Calling an LLM directly | DecisionCore |
|---|---|---|
| Data sovereignty | Your subject data is sent to an external AI cloud | Inference runs on TechnoNet's own servers in Kuala Lumpur, or on-premises with Enterprise |
| Audit trail | No record; regenerating produces a different answer | Audit ID and record fingerprint per explanation — verifiable |
| Regulatory format | Free-form text — no citations or rights notice for an auditor or regulator to check | Structured output: regulation citations, section numbers, rights notice |
| Consistency | Non-deterministic — same input, different output each time | Deterministic reasoning chain — same input produces the same structured record |
| PDF report | You build it yourself | Formal A4 PDF included — Audit ID, timestamp, subject rights, regulatory citations |
| AI governance readiness | You interpret the rules yourself | Corpus pre-mapped to the AIGE principles, PDPA, FSA, Employment Act and academic regulations; the proposed AI Governance Bill tracked as it develops |
{
"audit_id": "ADMT-EXP-8a2f1c3d",
"decision": "Loan application rejected",
"explanation": "The application was declined because the applicant's Debt Service Ratio (DSR) of 62% exceeds the institution's affordability limit of 60% (Credit Policy §3.1). At this ratio, more than 60% of monthly income would go to debt repayments. The applicant may ask for a human review of this automated decision.",
"regulations_cited": [
"Credit Policy §3.1 — Affordability limit (DSR)",
"Credit Policy §5 — Human review on request",
"AIGE principles — Transparency"
],
"reasoning_chain": "THOUGHT: DSR is 62%. The institution's limit is 60%. Exceeded by 2 percentage points. Credit Policy §3.1 applies. CONCLUSION: reject — affordability limit exceeded.",
"report_url": "/explain/report/ADMT-EXP-8a2f1c3d",
"results_used": 1,
"results_remaining": 9
}
| Status | Meaning | Resolution |
|---|---|---|
| 400 | Bad Request — missing required field or invalid input | Check that decision and subject_description are present and non-empty. |
| 401 | Unauthorised — missing or invalid API key | Include Authorization: Bearer dc-… header. Verify key in dashboard. |
| 402 | Payment Required — result balance is zero | Top up at /portal. No plan change required. |
| 422 | Unprocessable Entity — input validation failure | Check field types. sector must be one of: financial, hr, academic. |
| 429 | Too Many Requests — rate limit exceeded | Rate limit: 60 requests/minute per API key. Add exponential backoff. |
| 500 | Internal Server Error — reasoning pipeline failure | Retry once. If persistent, contact [email protected] with the request body. |
| 503 | Service Unavailable — inference server starting up | First cold-start can take 30–60s. Retry after 10 seconds. |