API Help Guide

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

  1. 1Create an API key from Console > Administration > API Keys.
  2. 2Store the full key in your secrets manager (it is shown only once).
  3. 3Send the key in the X-API-Key header on every request.
  4. 4Register the AI model and vendor you use at /models and /vendors.
  5. 5Issue a certificate at /certificates with its evidences.
  6. 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 /webhooks register endpoint (returns secret once).
  • - GET /webhooks list endpoints + latest delivery info.
  • - DELETE /webhooks/:id remove endpoint.
  • - GET /webhooks/:id/deliveries?page=1&pageSize=20 inspect 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-Key header: the same key with the same body returns the same batch; the same key with a different body answers 409.
  • Follow the batch with GET /certificates/batch/{batchId}: its status (queued, processing, done or failed), the counts (total, processed, succeeded, failed) and one result per record with its certificateId, duplicate or error. Or subscribe to the certificate.batch_completed webhook.
  • Give every record your own clientRecordId (for example, your evaluation id), also in POST /certificates. Sealing the same id again answers 409 with the existing certificateId, so retries never create duplicates.
  • SDKs (version 0.2.0 or later): sealRecords / waitForRecordBatch in TypeScript and seal_records / wait_for_record_batch in Python.

Endpoints

  • - POST /certificates/batch queue up to 500 records; answers 202 with a batchId.
  • - 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 a kind (model_call, tool_call, action, retrieval, decision or human_intervention), a name, inputHash / outputHash, a status (ok, error or blocked), startedAt / endedAt and metadata.
  • - 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 document needs at least one input and one output, and every output needs a filename.
  • - 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, purpose and 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.
  • publicMetadata is what you choose to show on public verification, signed in clear. Never put personal data in it.
  • POST /certificates/{certificateId}/erase-personal-data removes 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).

A record proves the integrity, origin and time of what was declared. It never proves that its content is correct.

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}/export bundle for the owner (API key), with the private payload of minimal-disclosure records.
  • - GET /certificates/{certificateId}/public-export public bundle, no API key.
  • - GET /.well-known/needine-keys.json Needine's public signing keys.
  • - GET /schemas/evidence-record-v2.json public 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 429 and "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 /vendors create a vendor (name, type, website, contactEmail).
  • - GET /vendors list vendors with optional filters (type, reviewStatus, dpaStatus).
  • - PUT /vendors/:id update vendor info, DPA status, certifications, risk score.
  • - DELETE /vendors/:id remove a vendor.
  • - GET /vendors/:id/models list AI models linked to a specific vendor.
  • - GET /vendors/export export 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-classification set GPAI status and compute threshold.
  • - PUT /models/:id/inventory update use case, department, responsible person, risk level.
  • - GET /models/inventory/export export full model registry as CSV or JSON.

Model performance endpoints

  • - POST /models/:modelId/performance record an evaluation (accuracy, precision, recall, F1, AUC).
  • - GET /models/:modelId/performance list evaluation history (paginated).
  • - GET /models/:modelId/performance/latest get the most recent evaluation.
  • - GET /models/:modelId/performance/drift compare 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/transparency create a transparency record.
  • - GET /certificates/:id/transparency get the transparency record.
  • - PUT /certificates/:id/transparency update 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-doc create technical documentation.
  • - GET /certificates/:id/technical-doc retrieve documentation.
  • - PUT /certificates/:id/technical-doc update documentation fields.
  • - GET /certificates/:id/technical-doc/annex-iv check 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/fria create a FRIA.
  • - GET /compliance/fria list all FRIAs.
  • - PUT /compliance/fria/:id update FRIA details.
  • - PATCH /compliance/fria/:id/review mark as reviewed.

Impact categories: privacy, non-discrimination, freedom, dignity, consumer protection. Status: DRAFT → COMPLETED → REVIEWED.

Risk Management, Monitoring, Literacy & Incidents

  • - POST/PUT /compliance/risk-management manage risks by phase (design → post-market).
  • - POST/PUT /compliance/monitoring-plans create post-market monitoring plans.
  • - POST/GET /compliance/literacy record AI literacy training completions.
  • - POST/PUT /compliance/incidents report compliance incidents.
  • - POST/PUT /compliance/qms quality 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/trigger trigger blockchain anchoring for pending certificates.
  • - GET /anchors/:id/verify verify 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-act risk classification by certificate type.
  • - GET /reports/eu-ai-act-full full compliance bundle (all modules combined).
  • - GET /reports/compliance-score 0–100 score of what you have documented, with recommendations (an internal indicator, not a certification).
  • - GET /reports/fria-summary FRIA count by status.
  • - GET /reports/risk-management-summary risks by phase.
  • - GET /reports/transparency-summary transparency records count.
  • - GET /reports/monitoring-summary active monitoring plans.
  • - GET /reports/incidents-summary incidents by status.
  • - GET /reports/qms-summary QMS documentation status.
  • - GET /reports/literacy-summary AI training completion percentage.
  • - GET /reports/technical-doc-coverage technical documentation completeness.
  • - GET /reports/model-performance model performance stats.
  • - GET /reports/gdpr-trail GDPR audit trail.
  • - GET /reports/declaration export 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; minimal keeps 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-data removes personal data (GDPR art. 17); signature and anchor keep verifying. ADMIN, scope certificates.erase.
  • - GET /verification/:certificateId/qr.svg public QR code (SVG) of the verification page, to embed in your PDFs.
  • - GET /.well-known/needine-keys.json Needine public signing keys, to verify signatures offline.
  • - GET /organizations/current/usage?months=6 certificates issued per month and real anchoring fee (total and per certificate).
  • - PATCH /organizations/current/verification-settings public 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.

Integration playbook

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 typeHow they call NeedineGo to pattern
Modern customer with their own backendDirect REST API or SDKPattern A
Customer with Java / .NET / Python / Node stackSDK or HTTP clientPattern B
Customer with manual or low-code processesMiddleware / connectorPattern C
Customer with ERP / CRMAdapter between ERP and NeedinePattern D
Customer with closed infrastructurePrivate integration, VPN, allowlist or dedicated deploymentPattern E
Customer without enough technical capacityAssisted integration servicePattern F
Before you start: run a 60-90 min Integration Assessment to answer: which event triggers certification?, which evidence can be hashed in place?, can the system make outbound HTTPS?, can it receive webhooks?, are there SSO / proxy / firewall / data residency constraints?, who signs or approves?, where is the 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:
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_CERTID in the master table.

Salesforce

  • • Apex callout with Named Credential (manages API key).
  • • Trigger after insert/update → async call (@future or 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() or action_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

PathTimeCustomer profile
QuickStart API1-3 daysModern backend, 1 generalist developer (Pattern A/B)
Assisted connector1-3 weeksERP/CRM, low-code (Pattern C/D)
Custom integration4-12 weeksRestricted 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 at https://api.needine.com/api/docs-json to 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.

Open Swagger / OpenAPI