API usage and setup guide
Needine integrates with systems you already use. This guide explains how to activate API keys, authenticate requests, and issue your first certificate with verifiable traceability.
1. Prerequisites
- - Active Needine account.
- - Role with API key permissions (typically ADMIN).
- - An HTTP client environment (Postman, curl, or your backend).
- - API base URL for your deployment.
- - Rotated secret storage for API keys (Vault, AWS Secrets Manager, etc.).
Production defaults
- - Base URL:
https://api.needine.com - - Auth header:
X-API-Key: ndi_... - - Content type:
application/json
2. Quickstart in 6 steps
- 1Create an API key from Console > Administration > API Keys.
- 2Store the full key in your secrets manager (it is shown only once).
- 3Send the key in the X-API-Key header on every request.
- 4Register the AI model and vendor you use at /models and /vendors.
- 5Issue a certificate at /certificates with its evidences.
- 6Share the public verification link /verify/:id with third parties.
3. API key authentication
All machine-to-machine API calls must include the X-API-Key. Needine validates key prefix + SHA-256 hash and applies permissions from the key owner user.
Curl example
curl -X GET "https://your-api-domain/certificates?page=1&pageSize=20" \ -H "X-API-Key: ndi_xxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json"
Node.js fetch example
const res = await fetch("https://api.needine.com/certificates?page=1&pageSize=10", {
headers: {
"X-API-Key": process.env.NEEDINE_API_KEY,
"Content-Type": "application/json"
}
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();4. Recommended API flow
Step A
Register vendor
POST /vendors
Step B
Register model
POST /models
Step C
Issue certificate
POST /certificates
Step D
Get traceability
GET /certificates/:id/provenance
Step E
Public verification
GET /verification/:id
5. Minimum payload to issue a certificate
This sample uses fields validated by the current API contract. Adjust by your workflow and always validate in Swagger before go-live.
Valid certificateType values
ai_report | ai_audit | ai_model_card | ai_dataset_card | ai_evaluation | generic
{
"certificateType": "ai_report",
"reportName": "Quarterly AI Risk Summary",
"signerName": "Maria Lopez",
"issuingCompany": "Acme Consulting",
"inputEvidence": [
{
"evidenceType": "input",
"sha256Hash": "b7c4f4...64hex",
"mimeType": "application/json",
"filename": "input.json",
"storageMode": "hash_only"
}
],
"outputEvidence": [
{
"evidenceType": "output",
"sha256Hash": "a99d2a...64hex",
"mimeType": "application/pdf",
"filename": "output.pdf",
"storageMode": "hash_only"
}
],
"model": {
"provider": "openai",
"modelName": "gpt-4o"
}
}6. End-to-end examples for backend teams
Use one of these snippets to issue and verify certificates from your integration layer.
curl (issue certificate)
curl -X POST "https://api.needine.com/certificates" \
-H "X-API-Key: ndi_xxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"certificateType": "ai_report",
"signerName": "Maria Lopez",
"issuingCompany": "Acme Consulting",
"inputEvidence": [{"sha256Hash": "b7c4...", "evidenceType": "input"}],
"outputEvidence": [{"sha256Hash": "a99d...", "evidenceType": "output", "filename": "report.pdf"}],
"model": {"provider": "openai", "modelName": "gpt-4o"}
}'JavaScript SDK
import { NeedineClient } from "@needine/sdk";
const client = new NeedineClient({ apiKey: process.env.NEEDINE_API_KEY });
const created = await client.createCertificate({
certificateType: "ai_report",
signerName: "Maria Lopez",
issuingCompany: "Acme Consulting",
inputEvidence: [{ sha256Hash: "b7c4...", evidenceType: "input" }],
outputEvidence: [{ sha256Hash: "a99d...", evidenceType: "output", filename: "report.pdf" }],
model: { provider: "openai", modelName: "gpt-4o" }
});
const verification = await client.verifyCertificate(created.certificateId);
console.log(created.certificateId, verification.valid, verification.trustLevel);Python SDK
from needine import NeedineClient
client = NeedineClient(api_key="ndi_xxx", base_url="https://api.needine.com")
created = client.create_certificate({
"certificateType": "ai_report",
"signerName": "Maria Lopez",
"issuingCompany": "Acme Consulting",
"inputEvidence": [{"sha256Hash": "b7c4...", "evidenceType": "input"}],
"outputEvidence": [{"sha256Hash": "a99d...", "evidenceType": "output", "filename": "report.pdf"}],
"model": {"provider": "openai", "modelName": "gpt-4o"}
})
verification = client.verify_certificate(created["certificateId"])
print(created["certificateId"], verification["valid"], verification["trustLevel"])7. Webhooks (optional)
Webhooks let your backend react in near real-time when certificate state changes. Register endpoint URLs from the Console (or API), subscribe to events, and process callbacks asynchronously in your integration layer.
Webhook management endpoints
- -
POST /webhooksregister endpoint (returns secret once). - -
GET /webhookslist endpoints + latest delivery info. - -
DELETE /webhooks/:idremove endpoint. - -
GET /webhooks/:id/deliveries?page=1&pageSize=20inspect delivery logs.
Supported event names
certificate.issued, certificate.revoked, certificate.personal_data_erased, certificate.superseded, anchor.confirmed, certificate.timestamped, certificate.batch_completed, draft.created, draft.approved, draft.rejected, model.performance.drift, ai_system.bias_flagged, human_review.resolved
Needine sends each of these events when it happens; subscribe only to the ones you need. certificate.batch_completed tells you a batch has finished (section 8).
Delivery contract
- - Method: POST
- - Content-Type: application/json
- - Timeout per attempt: 10s
- - Retries: 3 attempts with backoff 1s, 4s, 9s
- - Success criteria: any HTTP 2xx response
- - Recommended receiver behavior: idempotent processing keyed by event + certificate ID.
Headers and body sent by Needine
POST https://your-system.example/webhooks/needine
Content-Type: application/json
X-Needine-Event: certificate.issued
X-Needine-Delivery: <delivery id>
X-Needine-Signature: sha256=<hex>
X-Needine-Signature-Key: secret
{
"id": "<delivery id, use it to ignore retries you already processed>",
"event": "certificate.issued",
"payload": {
"certificateId": "cert_xxx",
"certificateType": "ai_report",
"status": "issued",
"issuedAt": "2026-03-18T19:48:08.800Z",
"signerName": "Maria Lopez",
"issuingCompany": "Acme Consulting",
"verificationUrl": "https://needine.com/verify/cert_xxx"
},
"timestamp": "2026-03-18T19:48:09.120Z"
}Signature verification (Node.js)
import crypto from "crypto";
const rawSecret = process.env.NEEDINE_WEBHOOK_SECRET;
const signatureHeader = req.headers["x-needine-signature"]; // sha256=<hex>
const rawBody = requestRawBodyAsString;
// Endpoints created before 2026-10 send "legacy-secret-sha256" until their secret is rotated
const signingKey = req.headers["x-needine-signature-key"] === "legacy-secret-sha256"
? crypto.createHash("sha256").update(rawSecret).digest("hex")
: rawSecret;
const expected = crypto.createHmac("sha256", signingKey).update(rawBody).digest("hex");
const received = String(signatureHeader || "").replace("sha256=", "");
if (!crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(received, "hex"))) {
throw new Error("Invalid webhook signature");
}Signature verification (Python)
import hashlib
import hmac
raw_secret = os.environ["NEEDINE_WEBHOOK_SECRET"]
signature_header = request.headers.get("X-Needine-Signature", "")
raw_body = request.get_data(as_text=True)
# Endpoints created before 2026-10 send "legacy-secret-sha256" until their secret is rotated
if request.headers.get("X-Needine-Signature-Key") == "legacy-secret-sha256":
signing_key = hashlib.sha256(raw_secret.encode()).hexdigest()
else:
signing_key = raw_secret
expected = hmac.new(signing_key.encode(), raw_body.encode(), hashlib.sha256).hexdigest()
received = signature_header.replace("sha256=", "")
if not hmac.compare_digest(expected, received):
raise ValueError("Invalid webhook signature")8. Seal many records: batches and safe retries
POST /certificates/batch takes { "records": [...] } with up to 500 records, each one exactly like a POST /certificates body, and answers 202 with a batchId. Needine seals them in the background: each record is signed, timestamped and anchored like a single one.
- Send an
Idempotency-Keyheader: the same key with the same body returns the same batch; the same key with a different body answers409. - Follow the batch with
GET /certificates/batch/{batchId}: itsstatus(queued,processing,doneorfailed), the counts (total,processed,succeeded,failed) and one result per record with itscertificateId,duplicateorerror. Or subscribe to thecertificate.batch_completedwebhook. - Give every record your own
clientRecordId(for example, your evaluation id), also inPOST /certificates. Sealing the same id again answers409with the existingcertificateId, so retries never create duplicates. - SDKs (version 0.2.0 or later):
sealRecords/waitForRecordBatchin TypeScript andseal_records/wait_for_record_batchin Python.
Endpoints
- -
POST /certificates/batchqueue up to 500 records; answers 202 with abatchId. - -
GET /certificates/batch/{batchId}status, counts and one result per record.
Example: queue a batch
curl -X POST "https://api.needine.com/certificates/batch" \
-H "X-API-Key: ndi_xxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: nightly-2026-10-03" \
-d '{
"records": [
{
"certificateType": "ai_evaluation",
"recordKind": "inference",
"clientRecordId": "eval-0042",
"signerName": "Maria Lopez",
"issuingCompany": "Acme Learning",
"inputEvidence": [{"sha256Hash": "b7c4...", "evidenceType": "input"}],
"outputEvidence": [{"sha256Hash": "a99d...", "evidenceType": "output"}]
}
]
}'
HTTP/1.1 202 Accepted
{ "batchId": "5b2f0c1e-8a4d-4f7e-9c11-2d6a3e7b9f20", "status": "queued", "total": 1, "processed": 0, ... }Example: GET /certificates/batch/{batchId} when the batch is done
{
"batchId": "5b2f0c1e-8a4d-4f7e-9c11-2d6a3e7b9f20",
"status": "done",
"total": 3,
"processed": 3,
"succeeded": 2,
"failed": 1,
"results": [
{ "index": 0, "certificateId": "cert_9f1c..." },
{ "index": 1, "certificateId": "cert_4a7e...", "duplicate": true },
{ "index": 2, "error": "AI system version not found" }
],
"errorMessage": null,
"createdAt": "2026-10-03T22:00:00.000Z",
"finishedAt": "2026-10-03T22:00:04.000Z"
}TypeScript and Python SDKs
// TypeScript (@needine/sdk)
const records = evaluations.map((e) => ({
certificateType: "ai_evaluation",
recordKind: "inference",
clientRecordId: e.id, // a retry never seals the same evaluation twice
signerName: "Maria Lopez",
issuingCompany: "Acme Learning",
inputEvidence: [{ sha256Hash: e.inputHash, evidenceType: "input" }],
outputEvidence: [{ sha256Hash: e.outputHash, evidenceType: "output" }],
}));
const batch = await client.sealRecords(records, { idempotencyKey: "nightly-2026-10-03" });
const done = await client.waitForRecordBatch(batch.batchId);
console.log(done.status, done.succeeded, done.failed);
# Python (needine-sdk)
batch = client.seal_records(records, idempotency_key="nightly-2026-10-03")
done = client.wait_for_record_batch(batch["batchId"])
print(done["status"], done["succeeded"], done["failed"])9. Evidence Records: inferences, agent runs and human reviews
A record can seal more than a document. recordKind says what it seals: document (default), inference, agent_run, retrieval, human_review or incident. These optional fields describe the execution and are signed with the record.
Optional fields of POST /certificates
- -
purpose: why the AI ran. - -
runId: groups the records of one execution (for example, an agent run). - -
policyRefs: identifiers of the policies that applied. - -
steps: the steps of the execution, in order. Each step has akind(model_call,tool_call,action,retrieval,decisionorhuman_intervention), aname,inputHash/outputHash, astatus(ok,errororblocked),startedAt/endedAtandmetadata. - -
aiSystemVersionId: links the record to a registered AI system version. While an AI system is frozen, it only accepts records from its frozen version.
Validation rules
- - A
documentneeds at least one input and one output, and every output needs afilename. - - Any other kind needs at least one input and either an output or a step.
Example: an inference record with two steps
{
"certificateType": "ai_evaluation",
"recordKind": "inference",
"clientRecordId": "eval-0042",
"runId": "run_7f3a",
"purpose": "Oral level assessment",
"policyRefs": ["POL-HUMAN-REVIEW-01"],
"signerName": "Maria Lopez",
"issuingCompany": "Acme Learning",
"model": { "provider": "openai", "modelName": "gpt-4o" },
"inputEvidence": [{ "evidenceType": "input", "sha256Hash": "b7c4f4...64hex" }],
"outputEvidence": [{ "evidenceType": "output", "sha256Hash": "a99d2a...64hex" }],
"steps": [
{
"kind": "model_call",
"name": "gpt-4o",
"inputHash": "b7c4f4...64hex",
"outputHash": "5e1d9c...64hex",
"status": "ok",
"startedAt": "2026-10-03T10:00:00Z",
"endedAt": "2026-10-03T10:00:02Z"
},
{
"kind": "decision",
"name": "assign_level",
"inputHash": "5e1d9c...64hex",
"outputHash": "a99d2a...64hex",
"status": "ok"
}
]
}10. Privacy: minimal disclosure and erasure
Needine only receives hashes and the context you send. These options keep personal data out of what is signed in clear and out of public verification.
- With
"disclosure": "minimal", the report name, file names, metadata,purposeand the steps' metadata go to a private payload. The signed record keeps only a salted commitment to it, and public verification never shows it; the owner's export includes it. publicMetadatais what you choose to show on public verification, signed in clear. Never put personal data in it.POST /certificates/{certificateId}/erase-personal-dataremoves the personal data (GDPR art. 17); the record stays verifiable.
Example: a minimal-disclosure record, then erasure
{
"certificateType": "ai_evaluation",
"recordKind": "inference",
"disclosure": "minimal",
"purpose": "Oral level assessment of a candidate",
"metadata": { "candidateName": "Jane Doe" },
"publicMetadata": { "level": "B2", "assessedAt": "2026-10-01" },
"signerName": "Maria Lopez",
"issuingCompany": "Acme Learning",
"inputEvidence": [{ "sha256Hash": "b7c4...", "evidenceType": "input", "filename": "recording.webm" }],
"outputEvidence": [{ "sha256Hash": "a99d...", "evidenceType": "output" }]
}
curl -X POST "https://api.needine.com/certificates/cert_xxx/erase-personal-data" \
-H "X-API-Key: ndi_xxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"reason": "GDPR art. 17 request from the data subject"}'11. What a record proves, and when
Each layer of proof is added at a different moment:
At issuance
Integrity and origin: Needine signs the canonical record with Ed25519.
Within the hour
Time: an RFC 3161 timestamp over the Merkle root of the hour's batch, issued by a time-stamping authority (TSA). The token is public (GET /timestamps/{batchId}/token.tst), and the verification shows whether the authority is a qualified service in the EU trusted lists.
Periodically
The organization's Merkle root is anchored on Arbitrum One (NeedineAnchor contract).
12. Verify without Needine
Anyone can check a record without an account and without trusting Needine.
Endpoints
- -
GET /verification/{certificateId}public verification, no API key. Add /badge.svg for the embeddable badge or /qr.svg for the QR code. - -
GET /certificates/{certificateId}/exportbundle for the owner (API key), with the private payload of minimal-disclosure records. - -
GET /certificates/{certificateId}/public-exportpublic bundle, no API key. - -
GET /.well-known/needine-keys.jsonNeedine's public signing keys. - -
GET /schemas/evidence-record-v2.jsonpublic JSON Schema of the signed record.
Offline checks with the SDKs (version 0.2.0 or later)
- -
verifyBundle/verify_bundle: signed payload, Ed25519 signature and Merkle proofs, against the keys at/.well-known/needine-keys.json. - -
verifyBundleTimestamp/verify_bundle_timestamp: the RFC 3161 token. - -
verifyBundleQualification/verify_bundle_qualification: whether the authority was a qualified service in the EU trusted lists on the date of the stamp.
The signed record follows a public JSON Schema, https://api.needine.com/schemas/evidence-record-v2.json, also included in both SDKs (EVIDENCE_RECORD_V2_SCHEMA in TypeScript, evidence_record_schema() in Python).
Example (TypeScript)
import { verifyBundle, verifyBundleTimestamp, verifyBundleQualification } from "@needine/sdk";
const api = "https://api.needine.com";
const bundle = await (await fetch(`${api}/certificates/${certificateId}/public-export`)).json();
const { keys } = await (await fetch(`${api}/.well-known/needine-keys.json`)).json();
const record = verifyBundle(bundle, keys); // payload, signature and Merkle proofs
const timestamp = await verifyBundleTimestamp(bundle); // RFC 3161 token
const tsa = await verifyBundleQualification(bundle); // EU trusted lists
console.log(record.valid, timestamp.valid, tsa.status);Example (Python)
import httpx
from needine import verify_bundle, verify_bundle_timestamp, verify_bundle_qualification
api = "https://api.needine.com"
bundle = httpx.get(f"{api}/certificates/{certificate_id}/public-export").json()
keys = httpx.get(f"{api}/.well-known/needine-keys.json").json()["keys"]
record = verify_bundle(bundle, keys) # payload, signature and Merkle proofs
timestamp = verify_bundle_timestamp(bundle) # RFC 3161 token
tsa = verify_bundle_qualification(bundle) # EU trusted lists
print(record["valid"], timestamp["valid"], tsa["status"])13. Free, with fair use
- Needine is free: no plans and no card.
- Each organization has monthly fair-use limits on sealed records and public verifications. Going over them only alerts Needine; it never stops the service.
- Only a cap that Needine agrees with an organization refuses new records, with
429and"code": "fair_use_cap_reached". - You can see your usage in the console (Administration).
14. AI vendors API
Track third-party AI vendors, their DPA status, certifications, and risk scores. Use these endpoints to maintain supply chain transparency as required by the EU AI Act.
Vendor management endpoints
- -
POST /vendorscreate a vendor (name, type, website, contactEmail). - -
GET /vendorslist vendors with optional filters (type, reviewStatus, dpaStatus). - -
PUT /vendors/:idupdate vendor info, DPA status, certifications, risk score. - -
DELETE /vendors/:idremove a vendor. - -
GET /vendors/:id/modelslist AI models linked to a specific vendor. - -
GET /vendors/exportexport vendor registry as CSV or JSON.
Vendor types
API_PROVIDER,PLATFORM,SELF_HOSTED,OPEN_SOURCE
DPA status workflow
NOT_REVIEWED →PENDING_SIGNATURE →SIGNED →EXPIRED
15. Model inventory and performance API
Extend model records with GPAI classification, inventory fields, and performance evaluations. Needine automatically detects metric drift between consecutive evaluations.
Model inventory endpoints
- -
PUT /models/:id/gpai-classificationset GPAI status and compute threshold. - -
PUT /models/:id/inventoryupdate use case, department, responsible person, risk level. - -
GET /models/inventory/exportexport full model registry as CSV or JSON.
Model performance endpoints
- -
POST /models/:modelId/performancerecord an evaluation (accuracy, precision, recall, F1, AUC). - -
GET /models/:modelId/performancelist evaluation history (paginated). - -
GET /models/:modelId/performance/latestget the most recent evaluation. - -
GET /models/:modelId/performance/driftcompare the latest two evaluations for metric drift.
Example: record a performance evaluation
curl -X POST "https://api.needine.com/models/model_xxx/performance" \
-H "X-API-Key: ndi_xxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"accuracy": 0.94,
"precision": 0.91,
"recall": 0.89,
"f1Score": 0.90,
"auc": 0.96,
"datasetName": "Q1-2026-validation",
"datasetSize": 12500,
"evaluatedBy": "data-science-team",
"notes": "Quarterly model evaluation"
}'16. EU AI Act compliance API
Dedicated endpoints to document your EU AI Act obligations. They help you document and evidence compliance; they do not certify it. All records are linked to certificates and models within your organization.
Transparency (Art. 13, 14, 50)
- -
POST /certificates/:id/transparencycreate a transparency record. - -
GET /certificates/:id/transparencyget the transparency record. - -
PUT /certificates/:id/transparencyupdate transparency info.
Fields: interactionType (CHATBOT, DEEPFAKE, EMOTION, BIOMETRIC, GENERATED_CONTENT), humanOversightType (HITL, HOTL, HIC), disclosure, override process, stop mechanism.
Technical Documentation (Art. 11, Annex IV)
- -
POST /certificates/:id/technical-doccreate technical documentation. - -
GET /certificates/:id/technical-docretrieve documentation. - -
PUT /certificates/:id/technical-docupdate documentation fields. - -
GET /certificates/:id/technical-doc/annex-ivcheck how complete the Annex IV documentation is.
Fields: system name, intended purpose, deployers, design specs, data governance, validation, performance metrics, risk mitigations.
FRIA — Fundamental Rights Impact Assessment (Art. 6, 27)
- -
POST /compliance/friacreate a FRIA. - -
GET /compliance/frialist all FRIAs. - -
PUT /compliance/fria/:idupdate FRIA details. - -
PATCH /compliance/fria/:id/reviewmark as reviewed.
Impact categories: privacy, non-discrimination, freedom, dignity, consumer protection. Status: DRAFT → COMPLETED → REVIEWED.
Risk Management, Monitoring, Literacy & Incidents
- -
POST/PUT /compliance/risk-managementmanage risks by phase (design → post-market). - -
POST/PUT /compliance/monitoring-planscreate post-market monitoring plans. - -
POST/GET /compliance/literacyrecord AI literacy training completions. - -
POST/PUT /compliance/incidentsreport compliance incidents. - -
POST/PUT /compliance/qmsquality management system records.
17. Blockchain anchoring API
Certificates are anchored on Arbitrum One via Merkle trees. Use these endpoints to trigger anchoring manually or verify an existing anchor on-chain.
Anchoring endpoints
- -
POST /anchors/triggertrigger blockchain anchoring for pending certificates. - -
GET /anchors/:id/verifyverify an anchor on-chain (returns Merkle root, tx hash, block number).
Anchor status: PENDING → SUBMITTED → CONFIRMED (or FAILED with automatic retries).
18. Reports and analytics API
Generate compliance and operational reports programmatically. All report endpoints accept optional date range query parameters.
Available report endpoints
- -
GET /reports/eu-ai-actrisk classification by certificate type. - -
GET /reports/eu-ai-act-fullfull compliance bundle (all modules combined). - -
GET /reports/compliance-score0–100 score of what you have documented, with recommendations (an internal indicator, not a certification). - -
GET /reports/fria-summaryFRIA count by status. - -
GET /reports/risk-management-summaryrisks by phase. - -
GET /reports/transparency-summarytransparency records count. - -
GET /reports/monitoring-summaryactive monitoring plans. - -
GET /reports/incidents-summaryincidents by status. - -
GET /reports/qms-summaryQMS documentation status. - -
GET /reports/literacy-summaryAI training completion percentage. - -
GET /reports/technical-doc-coveragetechnical documentation completeness. - -
GET /reports/model-performancemodel performance stats. - -
GET /reports/gdpr-trailGDPR audit trail. - -
GET /reports/declarationexport compliance declaration.
Privacy, corrections and public verification
Optional fields of POST /certificates and endpoints to keep personal data out of the public record, correct results and embed verification in your own documents.
Fields of POST /certificates
- -
disclosure:full(default) signs everything in clear;minimalkeeps metadata, reportName and evidence filenames private and signs only a salted SHA-256 commitment to them. - -
publicMetadata: fields shown on public verification, signed in clear (max 4 KB). Never put personal data here. - -
aiSystemVersionId: AI system version that produced the result (POST /ai-systems/:id/versions). Signed into the payload. - -
supersedesCertificateId+supersessionReason(human_review,appeal,error_correction): the new certificate replaces an issued one, which becomes SUPERSEDED.
Endpoints
- -
POST /certificates/:certificateId/erase-personal-dataremoves personal data (GDPR art. 17); signature and anchor keep verifying. ADMIN, scope certificates.erase. - -
GET /verification/:certificateId/qr.svgpublic QR code (SVG) of the verification page, to embed in your PDFs. - -
GET /.well-known/needine-keys.jsonNeedine public signing keys, to verify signatures offline. - -
GET /organizations/current/usage?months=6certificates issued per month and real anchoring fee (total and per certificate). - -
PATCH /organizations/current/verification-settingspublic presentation mode: certificate or integrity_seal. ADMIN.
19. Common errors and fixes
202 Accepted
Cause: Not an error: POST /certificates/batch queued the records, which are not sealed yet.
Fix: Follow GET /certificates/batch/{batchId} or wait for the certificate.batch_completed webhook.
401 Unauthorized
Cause: API key missing, revoked, or expired.
Fix: Check the X-API-Key header and key status in Console.
403 Forbidden
Cause: The API key does not have scope for this action.
Fix: Create a key with the correct scopes or adjust owner permissions.
404 Not Found
Cause: Invalid certificate ID or out-of-organization resource.
Fix: Confirm you are using the exact certificateId returned at issuance.
409 Conflict
Cause: This clientRecordId was already sealed (the response includes the existing certificateId), or an Idempotency-Key was reused with a different batch body.
Fix: For a duplicate record, keep the certificateId returned: the record is already sealed. For a batch, use a new key for a new body.
429 Too Many Requests
Cause: Too many requests in a short interval.
Fix: Apply retries with exponential backoff and retry queues.
429 · fair_use_cap_reached
Cause: Your organization reached the monthly cap it agreed with Needine.
Fix: Contact Needine to review the cap. Going over the fair-use limits alone never stops the service.
How to integrate Needine based on the customer's IT infrastructure
Needine plugs into the customer's existing systems: it does not require replacing the ERP, CRM, document platform, or AI pipeline. Identify your scenario in the table and follow the recommended pattern below.
| Customer type | How they call Needine | Go to pattern |
|---|---|---|
| Modern customer with their own backend | Direct REST API or SDK | Pattern A |
| Customer with Java / .NET / Python / Node stack | SDK or HTTP client | Pattern B |
| Customer with manual or low-code processes | Middleware / connector | Pattern C |
| Customer with ERP / CRM | Adapter between ERP and Needine | Pattern D |
| Customer with closed infrastructure | Private integration, VPN, allowlist or dedicated deployment | Pattern E |
| Customer without enough technical capacity | Assisted integration service | Pattern F |
certificateId stored?Pattern A — Modern customer with their own backend
Direct call to the REST API or use of the official SDK. This is the fastest pattern (1-3 days): event generated → local hash of evidence → POST to Needine → store certificateId → subscribe to webhooks.
Flow diagram
[Customer backend] │ 1. Generates AI output │ 2. sha256(input) + sha256(output) │ 3. POST /certificates (X-API-Key) ▼ [Needine API] -- 4. Returns certificateId, verificationUrl │ │ 5. anchor.confirmed (HMAC-signed webhook) ▼ [Customer backend] stores certificateId in its DB / ERP / CRM
Node / TypeScript SDK
import { NeedineClient } from "@needine/sdk";
import crypto from "crypto";
const client = new NeedineClient({ apiKey: process.env.NEEDINE_API_KEY! });
const sha256 = (buf: Buffer) => crypto.createHash("sha256").update(buf).digest("hex");
const cert = await client.createCertificate({
certificateType: "ai_report",
signerName: "Maria Lopez",
issuingCompany: "Acme SaaS",
inputEvidence: [{ sha256Hash: sha256(inputBuffer), evidenceType: "input" }],
outputEvidence: [{ sha256Hash: sha256(outputBuffer), evidenceType: "output", filename: "report.pdf" }],
model: { provider: "openai", modelName: "gpt-4o" },
});
await db.certificates.update({ where: { jobId }, data: { needineId: cert.certificateId } });Python SDK
from needine import NeedineClient
import hashlib, os
client = NeedineClient(api_key=os.environ["NEEDINE_API_KEY"])
def sha256(b: bytes) -> str:
return hashlib.sha256(b).hexdigest()
cert = client.create_certificate({
"certificateType": "ai_report",
"signerName": "Maria Lopez",
"issuingCompany": "Acme SaaS",
"inputEvidence": [{"sha256Hash": sha256(input_bytes), "evidenceType": "input"}],
"outputEvidence": [{"sha256Hash": sha256(output_bytes), "evidenceType": "output", "filename": "report.pdf"}],
"model": {"provider": "openai", "modelName": "gpt-4o"},
})
print(cert["certificateId"], cert.get("verificationUrl"))Pattern B — Languages without an official SDK (Java, .NET, Go, PHP, Ruby)
While we publish native SDKs, any standard HTTP client works. The API is REST + JSON, with OpenAPI available at /api/docs, which lets you generate clients automatically with openapi-generator.
Java 17+ (HttpClient)
import java.net.http.*;
import java.net.URI;
var body = """
{
"certificateType": "ai_report",
"signerName": "Maria Lopez",
"issuingCompany": "Acme Industrial",
"inputEvidence": [{"sha256Hash":"%s","evidenceType":"input"}],
"outputEvidence": [{"sha256Hash":"%s","evidenceType":"output","filename":"report.pdf"}],
"model": {"provider":"openai","modelName":"gpt-4o"}
}
""".formatted(inputHash, outputHash);
var req = HttpRequest.newBuilder(URI.create("https://api.needine.com/certificates"))
.header("X-API-Key", System.getenv("NEEDINE_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
var res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());.NET 8 (HttpClient)
using System.Net.Http.Json;
var http = new HttpClient { BaseAddress = new Uri("https://api.needine.com/") };
http.DefaultRequestHeaders.Add("X-API-Key", Environment.GetEnvironmentVariable("NEEDINE_API_KEY"));
var payload = new {
certificateType = "ai_report",
signerName = "Maria Lopez",
issuingCompany = "Acme Bank",
inputEvidence = new[] { new { sha256Hash = inputHash, evidenceType = "input" } },
outputEvidence = new[] { new { sha256Hash = outputHash, evidenceType = "output", filename = "report.pdf" } },
model = new { provider = "openai", modelName = "gpt-4o" }
};
var res = await http.PostAsJsonAsync("certificates", payload);
res.EnsureSuccessStatusCode();
var cert = await res.Content.ReadFromJsonAsync<Dictionary<string, object>>();Go
payload := map[string]any{
"certificateType": "ai_report",
"signerName": "Maria Lopez",
"issuingCompany": "Acme Logistics",
"inputEvidence": []map[string]string{{"sha256Hash": inputHash, "evidenceType": "input"}},
"outputEvidence": []map[string]string{{"sha256Hash": outputHash, "evidenceType": "output", "filename": "report.pdf"}},
"model": map[string]string{"provider": "openai", "modelName": "gpt-4o"},
}
b, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://api.needine.com/certificates", bytes.NewReader(b))
req.Header.Set("X-API-Key", os.Getenv("NEEDINE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)PHP 8
$payload = [
"certificateType" => "ai_report",
"signerName" => "Maria Lopez",
"issuingCompany" => "Acme Legal",
"inputEvidence" => [["sha256Hash" => $inputHash, "evidenceType" => "input"]],
"outputEvidence" => [["sha256Hash" => $outputHash, "evidenceType" => "output", "filename" => "report.pdf"]],
"model" => ["provider" => "openai", "modelName" => "gpt-4o"],
];
$ch = curl_init("https://api.needine.com/certificates");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["X-API-Key: " . getenv("NEEDINE_API_KEY"), "Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$cert = json_decode(curl_exec($ch), true);openapi-generator-cli generate -i https://api.needine.com/api/docs-json \ -g java -o ./needine-java # csharp-netcore, go, php, ruby, rust, kotlin, typescript-axios
Pattern C — Manual or low-code processes (Make, Zapier, n8n, Power Automate)
For customers without a development team, any automation platform can call Needine as a generic HTTP module. The customer only configures the URL, the API key, and the JSON.
Make / Zapier
- • Trigger: new file in Drive / Dropbox / SharePoint.
- • Step: HTTP module → POST
/certificates. - • Header:
X-API-Key. - • Output:
certificateId,verificationUrl. - • Action: append URL to file or send by email.
n8n (self-hosted)
- • Crypto node → SHA-256 of the file.
- • HTTP Request node → POST to Needine.
- • Webhook node → receive
certificate.issued. - • Set node → update your database / Notion / Airtable.
Microsoft Power Automate
- • Trigger: When a file is created (SharePoint).
- • Compute hash with
sha256(...)function. - • HTTP action to
/certificates. - • Update file metadata with
certificateId.
Google Apps Script
- • Trigger: onFormSubmit / onFileCreate.
- •
UrlFetchApp.fetch()with headers. - • Save response in Sheets / Drive.
Power Automate example (HTTP action)
Method: POST
URI: https://api.needine.com/certificates
Headers: X-API-Key : @{parameters('needine_api_key')}
Content-Type: application/json
Body:
{
"certificateType": "ai_report",
"signerName": "@{triggerBody()?['Editor']?['DisplayName']}",
"issuingCompany": "@{parameters('company')}",
"inputEvidence": [{"sha256Hash": "@{outputs('Hash_input')}", "evidenceType": "input"}],
"outputEvidence": [{"sha256Hash": "@{outputs('Hash_output')}", "evidenceType": "output", "filename": "@{triggerOutputs()?['body/{FilenameWithExtension}']}"}],
"model": { "provider": "openai", "modelName": "gpt-4o" }
}Pattern D — Customer with ERP / CRM (SAP, Salesforce, Dynamics, ServiceNow, Odoo)
The ERP core is not modified. A lightweight adapter (microservice or serverless function) is introduced to listen to the ERP event, normalize the payload, compute hashes, call Needine, and return the certificateId to the ERP to be saved in a custom field.
Adapter architecture
[ERP / CRM] │ Event (BAPI / Outbound Message / Business Rule / Webhook) ▼ [Needine adapter] ← customer or Needine microservice │ • ERP fields → Needine schema mapping │ • SHA-256 computation │ • Retries + queue (idempotency by externalId) │ • Vault for API key ▼ [Needine API] │ │ signed webhook ▼ [Adapter] → Update ERP record (custom field: needine_certificate_id)
SAP S/4HANA
- • SAP CPI / PI/PO with REST iflow.
- • OData Service exposes event → CPI calls Needine.
- • Custom field
ZNEEDINE_CERTIDin the master table.
Salesforce
- • Apex callout with
Named Credential(manages API key). - • Trigger after insert/update → async call (
@futureor Queueable). - • Platform Event to receive Needine webhooks.
Microsoft Dynamics 365
- • Power Automate flow (cloud) or C# plugin (sandbox).
- • Azure Function as adapter, invoked by Dataverse webhook.
ServiceNow
- • Business Rule + REST Message v2.
- • Custom IntegrationHub Spoke.
- • Inbound REST to receive webhooks.
Odoo
- • Custom Python module using
needine-sdk. - • Hook on
create()oraction_done().
Document management
- • SharePoint / M-Files / Alfresco / DocuWare.
- • Hook on file checkin → certify and save URL.
Apex (Salesforce) — example
public with sharing class NeedineService {
@future(callout=true)
public static void certify(Id reportId) {
Report__c r = [SELECT Id, OutputHash__c, InputHash__c FROM Report__c WHERE Id = :reportId];
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:Needine/certificates');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(new Map<String, Object>{
'certificateType' => 'ai_report',
'signerName' => UserInfo.getName(),
'issuingCompany' => 'Acme',
'inputEvidence' => new List<Object>{ new Map<String,String>{ 'sha256Hash' => r.InputHash__c, 'evidenceType' => 'input' } },
'outputEvidence' => new List<Object>{ new Map<String,String>{ 'sha256Hash' => r.OutputHash__c, 'evidenceType' => 'output', 'filename' => 'report.pdf' } },
'model' => new Map<String,String>{ 'provider' => 'openai', 'modelName' => 'gpt-4o' }
}));
HttpResponse res = new Http().send(req);
Map<String,Object> body = (Map<String,Object>) JSON.deserializeUntyped(res.getBody());
update new Report__c(Id = reportId, NeedineCertificateId__c = (String) body.get('certificateId'));
}
}Pattern E — Closed infrastructure (banking, healthcare, defense, public sector)
With strict network, data-residency or compliance constraints, this is what works today and what is not offered yet:
Your data stays with you
Only SHA-256 hashes and the metadata you choose are sent: files and prompts never leave your systems. Personal data can stay out of the signed record ("disclosure": "minimal") and be erased later (GDPR art. 17).
Outbound HTTPS only
Your systems only call https://api.needine.com on port 443; nothing has to reach into your network. The SDKs accept a custom baseUrl, and a corporate CA is trusted through your runtime (NODE_EXTRA_CA_CERTS in Node, SSL_CERT_FILE in Python).
No inbound webhooks (polling mode)
If the customer's network does not accept callbacks, their system polls every N minutes: GET /certificates/batch/{batchId} for its batches, or GET /certificates?page=1&pageSize=100 for the latest records (newest first).
Signed webhooks
If you do accept webhooks, every delivery is signed with HMAC-SHA256 in X-Needine-Signature; check it with verifyWebhookSignature / verify_webhook_signature before processing it.
Not available yet
Fixed outbound IP ranges, private link or VPN, mTLS, single sign-on with your identity provider and dedicated or on-premise deployment are not offered today. A local collector that hashes and signs inside your network is on the roadmap: tell us if you need any of them.
Polling (when no webhooks are available)
# Records you sealed in a batch curl "https://api.needine.com/certificates/batch/$BATCH_ID" \ -H "X-API-Key: $NEEDINE_API_KEY" # Latest records of the organization (newest first) curl -G "https://api.needine.com/certificates" \ -H "X-API-Key: $NEEDINE_API_KEY" \ --data-urlencode "page=1" \ --data-urlencode "pageSize=100"
Pattern F — Customer without technical capacity (assisted integration service)
If the customer has no development team, Needine delivers a turn-key solution that requires no programming:
- Needine web portal: the user uploads the file, the system computes the hash in the browser and issues the certificate without any coding.
- Assisted connector (on request): Needine or a partner builds the flow for you (for example in n8n, Make or Power Automate) and you only enter your API key. There are no published templates yet; the examples in this guide are the starting point.
- Managed service: Needine or a partner integrator runs the solution for the customer.
Three integration paths
| Path | Time | Customer profile |
|---|---|---|
| QuickStart API | 1-3 days | Modern backend, 1 generalist developer (Pattern A/B) |
| Assisted connector | 1-3 weeks | ERP/CRM, low-code (Pattern C/D) |
| Custom integration | 4-12 weeks | Restricted infrastructure or no technical team (Pattern E/F) |
Computing SHA-256 locally (evidence never leaves)
Needine receives the hash, not the content. Compute SHA-256 inside the customer's network and send only the lowercase hexadecimal digest (64 characters).
# bash
sha256sum file.pdf | awk '{print $1}'# PowerShell (Get-FileHash file.pdf -Algorithm SHA256).Hash.ToLower()
// Node
crypto.createHash("sha256").update(buf).digest("hex");# Python hashlib.sha256(data).hexdigest()
// Java
MessageDigest.getInstance("SHA-256").digest(bytes); // -> bytesToHex// C# Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant();
// Browser (no upload required)
const buf = await file.arrayBuffer();
const h = await crypto.subtle.digest("SHA-256", buf);
[...new Uint8Array(h)].map(b => b.toString(16).padStart(2,"0")).join("");// Go sum := sha256.Sum256(data) hex.EncodeToString(sum[:])
Integration best practices
Idempotency
Give each record a clientRecordId (your event id): sealing it again answers 409 with the existing certificateId, so retries never create duplicates. For batches, also send Idempotency-Key.
Retries with backoff
Retry only 429, 502, 503, 504, and network errors. Exponential backoff 1s, 2s, 4s, 8s (max. 5 attempts).
Rate limiting
Respect X-RateLimit-Remaining and Retry-After headers. Configure a client-side semaphore.
Pagination
?page=N&pageSize=50 (maximum 200). The response is { items, total, page, pageSize }.
API key storage
Vault / Azure Key Vault / AWS Secrets Manager / GCP Secret Manager. Never in repo or frontend.
Rotation
API keys: quarterly rotation. OAuth: POST /oauth/clients/:id/rotate-secret. Webhooks: regenerate the secret when security requests it.
Observability
Log the X-Request-Id returned by the API; it eases support. Health endpoint: /health.
Webhook validation
Verify HMAC before processing. Return 2xx in under 5s; any heavy logic goes to a queue.
Testing, OpenAPI, and public verification
- No separate sandbox yet: test with a dedicated test organization. Its records are real, verifiable certificates.
- Swagger / OpenAPI:
https://api.needine.com/api/docs, with the JSON athttps://api.needine.com/api/docs-jsonto generate clients. - Public verification without auth:
GET /verification/{certificateId}— useful for auditors or third parties. - Audit rooms: temporary tokens (
POST /audit-rooms) that grant read-only access to an auditor for X hours.
Integration checklist (before going to production)
- ✓Identified the business event that triggers certification
- ✓API key created and stored in the customer's vault
- ✓SHA-256 hash computed locally; raw content never leaves the customer
- ✓Source-system field mapping to the Needine schema documented
- ✓certificateId persisted in the source system (field or table)
- ✓Webhooks configured with verified HMAC, or active polling if no inbound callback
- ✓clientRecordId on every record (and Idempotency-Key on every batch)
- ✓Retries with exponential backoff only on 429/5xx
- ✓Logs include X-Request-Id for support
- ✓Tested end to end in a dedicated test organization (its records are real certificates) before going live
- ✓Rate limit measured and sized to the actual expected volume
- ✓API key and webhook secret rotation plan
- ✓Public verification tested by an external auditor
- ✓DPA / contracts signed if personal data is involved
Want us to help you integrate?
Book a 60-90 minute Integration Assessment with our technical team. We leave with the chosen pattern, the adapter defined, and a deployment plan.