# Quickstart — Certify Your First CER

URL: https://docs.nexart.io/docs/quickstart

Certify your first AI execution with NexArt in minutes: install the SDK, call certify, read the certificate hash and verify the resulting CER on the node.

This is the fastest correct integration path.

## NexArt Integration (Canonical Flow)

Flow:

- Capture execution input
- Seal locally (offline)
- Verify locally
- (Optional) Certify via node
- Verify again

Verification outcomes:

- Sealed → Integrity: PASS, Receipt: SKIPPED, Envelope: SKIPPED
- Certified → Integrity: PASS, Receipt: PASS, Envelope: PASS

Rules:

- Sealing does NOT require a node
- Certification is OPTIONAL
- Verification does NOT require trust
- SKIPPED is expected for local artifacts and is NOT a failure

Do NOT:

- call the node before sealing
- recompute hashes manually
- modify bundles after sealing
- treat SKIPPED as failure

The fastest path from zero to a verified record. For a broader overview, see Getting Started.

Verify Your First Execution (2 minutes)
Local-first. No API key, no node call, no network access. Seal a CER and verify it locally with the SDK.
1. Install

```
npm install @nexart/ai-execution
```

2. seal-and-verify.ts (single file, copy as-is)

```
import {
  certifyDecision,
  verifyAiCerBundleDetailed,
} from "@nexart/ai-execution";

async function main() {
  // Seal locally. Fully offline. certifyDecision is synchronous.
  const bundle = certifyDecision({
    provider:   "openai",
    model:      "gpt-4o-mini",
    prompt:     "Should this refund be approved?",
    input:      { messages: [{ role: "user", content: "Should this refund be approved?" }] },
    parameters: { temperature: 0, maxTokens: 1024, topP: null, seed: null },
    output:     { decision: "approve", reason: "policy_passed" },
  });

  console.log("certificateHash :", bundle.certificateHash);

  // Independent verification of the sealed bundle.
  const report = await verifyAiCerBundleDetailed(bundle);

  console.log("Integrity (Layer 1) :", report.checks.bundleIntegrity);
  console.log("Receipt   (Layer 2) :", report.checks.nodeSignature);
  console.log("Envelope  (Layer 3) :", report.checks.receiptConsistency);
}

main().catch((err) => {
  console.error("FAILED:", err);
  process.exit(1);
});
```

3. Run

```
npx tsx seal-and-verify.ts
```

Expected output (sealed bundle)

```
certificateHash : sha256:9f2b1c8e4a7d6f3b0c5e8a1d2f4b6c8e9a0d3f5b7c2e4a6d8f1b3c5e7a9d0f2b
Integrity (Layer 1) : PASS
Receipt   (Layer 2) : SKIPPED
Envelope  (Layer 3) : SKIPPED
```

Local sealing proves integrity. Certification adds a NexArt attestation node signature.

SKIPPED for Receipt and Envelope is expected. Those layers only apply after a node certifies the bundle. SKIPPED is not a failure.

Add node certification (optional)
Certification is optional. It submits the bundle to the attestation node, which adds an Ed25519 receipt and a verification envelope, and returns a public `verificationUrl`. The `certificateHash` does not change.
1. Configure node access

```
export NEXART_NODE_ENDPOINT="https://node.nexart.io"
export NEXART_API_KEY="<your-api-key>"
```

2. certify-and-verify.ts

```
import {
  certifyAndAttestDecision,
  verifyAiCerBundleDetailed,
} from "@nexart/ai-execution";

async function main() {
  // Seal + attest in one node round-trip. certifyAndAttestDecision is async.
  const { bundle, receipt } = await certifyAndAttestDecision(
    {
      provider:   "openai",
      model:      "gpt-4o-mini",
      prompt:     "Should this refund be approved?",
      input:      { messages: [{ role: "user", content: "Should this refund be approved?" }] },
      parameters: { temperature: 0, maxTokens: 1024, topP: null, seed: null },
      output:     { decision: "approve", reason: "policy_passed" },
    },
    {
      nodeUrl: process.env.NEXART_NODE_ENDPOINT!,
      apiKey:  process.env.NEXART_API_KEY!,
    },
  );

  const certificateHash = bundle.certificateHash;
  const verificationUrl = `https://verify.nexart.io/c/${certificateHash}`;

  console.log("certificateHash :", certificateHash);
  console.log("attestationId   :", receipt.attestationId);
  console.log("verificationUrl :", verificationUrl);

  const report = await verifyAiCerBundleDetailed(bundle);

  console.log("Integrity (Layer 1) :", report.checks.bundleIntegrity);
  console.log("Receipt   (Layer 2) :", report.checks.nodeSignature);
  console.log("Envelope  (Layer 3) :", report.checks.receiptConsistency);
}

main().catch((err) => {
  console.error("FAILED:", err);
  process.exit(1);
});
```

Expected output (certified bundle)

```
certificateHash : sha256:9f2b1c8e4a7d6f3b0c5e8a1d2f4b6c8e9a0d3f5b7c2e4a6d8f1b3c5e7a9d0f2b
verificationUrl : https://verify.nexart.io/c/sha256:9f2b1c8e4a7d6f3b0c5e8a1d2f4b6c8e9a0d3f5b7c2e4a6d8f1b3c5e7a9d0f2b
Integrity (Layer 1) : PASS
Receipt   (Layer 2) : PASS
Envelope  (Layer 3) : PASS
```

With node certification, all three layers return PASS. The `verificationUrl` is publicly resolvable at verify.nexart.io.

Mental model
- Logs describe. CERs prove.
- Integrity ≠ Stamp ≠ Envelope.
- Verification does not require trust.

## Quick Implementation Flow

The four steps every NexArt integration must perform, in order.

Quick Implementation Flow
The four steps required to integrate NexArt correctly. Each step has a single responsibility.
- Step 1Capture execution
What: Record provider, model, prompt, input, parameters, and output. All six are required on CertifyDecisionParams.
Why: The certificateHash is computed from this data. Capture must happen before sealing.
@nexart/signals · createContext() (optional) → passed into certifyDecision(...)
- Step 2Create CER
What: Seal a CER bundle and compute certificateHash over the strict whitelist (JCS).
Why: The hash is the canonical identity of the record. It binds the bundle to its content.
certifyDecision(params) · or sealCer(snapshot) for the lower-level path
- Step 3Certify via node
What: Submit the bundle to the attestation node. Receive a signed receipt and verification envelope.
Why: The node provides an independent witness and a public verification surface.
certifyAndAttestDecision(params, options) · or attest(bundle, options) · POST /v1/cer/ai/certify
- Step 4Verify independently
What: Anyone can re-derive certificateHash, validate the receipt signature, and validate the envelope.
Why: Verification requires no trust in your infrastructure or the node beyond its published key.
verifyAiCerBundleDetailed(bundle) · or https://verify.nexart.io/c/{bundle.certificateHash}

Deeper reference: Verification · Verification Layers · Hashes

## Minimal working example

The canonical example. Reuse this shape in your own integration.

How the SDK is split
- Creation is local and deterministic (`certifyDecision`). It produces a sealed bundle and a `certificateHash`. No network call.
- Certification is node-based (`certifyAndAttestDecision` or `attest(bundle, options)`). It returns an`AttestationReceipt` and the bundle gains a verification envelope.
- A public verification URL exists ONLY after certification. Construct it as `https://verify.nexart.io/c/{certificateHash}`.
- `prompt` and `parameters` are REQUIRED fields on `CertifyDecisionParams`, alongside `provider`, `model`, `input`, and `output`.

Flow A - Certified execution (recommended)

```
import {
  certifyAndAttestDecision,
  verifyAiCerBundleDetailed,
} from "@nexart/ai-execution";

// Steps 1-3: capture execution, seal the CER, attest via the node.
// certifyAndAttestDecision is async because it contacts the attestation node.
const { bundle, receipt } = await certifyAndAttestDecision(
  {
    provider:   "openai",
    model:      "gpt-4o-mini",
    prompt:     "Should this refund be approved?",
    input:      { messages: [{ role: "user", content: "Should this refund be approved?" }] },
    parameters: { temperature: 0, maxTokens: 1024, topP: null, seed: null },
    output:     { decision: "approve", reason: "policy_passed" },
  },
  {
    nodeUrl: process.env.NEXART_NODE_ENDPOINT!,
    apiKey:  process.env.NEXART_API_KEY!,
  },
);

const certificateHash = bundle.certificateHash;
const verificationUrl = `https://verify.nexart.io/c/${certificateHash}`;

console.log(certificateHash);         // sha256:...
console.log(receipt.attestationId);   // node-issued id
console.log(verificationUrl);         // https://verify.nexart.io/c/sha256:...

// Step 4: independent verification. No trust in your infrastructure required.
const report = await verifyAiCerBundleDetailed(bundle);
// report.checks.bundleIntegrity    -> PASS  (Layer 1)
// report.checks.nodeSignature      -> PASS  (Layer 2)
// report.checks.receiptConsistency -> PASS  (Layer 3)
```

Flow B - Local-only execution (no node interaction)

```
import { certifyDecision, verifyAiCerBundleDetailed } from "@nexart/ai-execution";

// certifyDecision is synchronous and deterministic.
// It produces a sealed CER bundle and certificateHash WITHOUT contacting the node.
// There is no receipt, no envelope, and no public verificationUrl in this flow.
const bundle = certifyDecision({
  provider:   "openai",
  model:      "gpt-4o-mini",
  prompt:     "Should this refund be approved?",
  input:      { messages: [{ role: "user", content: "Should this refund be approved?" }] },
  parameters: { temperature: 0, maxTokens: 1024, topP: null, seed: null },
  output:     { decision: "approve", reason: "policy_passed" },
});

const certificateHash = bundle.certificateHash;
console.log(certificateHash);   // sha256:...

// Local verification: Layer 1 PASS, Layers 2 & 3 SKIPPED.
const report = await verifyAiCerBundleDetailed(bundle);

// To obtain a signed receipt and a public verificationUrl, attest the bundle
// via certifyAndAttestDecision(...) or attest(bundle, options).
```

Using LangChain? See the dedicated LangChain integration page for the optional adapters `createLangChainCer` / `certifyLangChainRun` imported from `@nexart/ai-execution/langchain`.

## Choose a Path

- Path A - Single CER: certify one execution. The most common starting point.
- Path B - Project Bundle: certify a multi-step or multi-agent workflow as a single verifiable unit.

Project Bundles are not required for single-execution use cases.

## Path A: Single CER

Canonical workflow: create input → seal locally → verify → (optional) certify → verify again. Sealing is offline and requires no API key. Certification is optional and adds node attestation.

### 1. Install the SDK

```
npm install @nexart/ai-execution
```

Current version: `@nexart/ai-execution@1.2.0`.

### 2. Seal a CER locally (offline)

Seal a CER locally — no node, no API key

```
import { certifyDecision, verifyAiCerBundleDetailed } from "@nexart/ai-execution";

// certifyDecision (from @nexart/ai-execution) is synchronous.
const bundle = certifyDecision({
  provider:   "openai",
  model:      "gpt-4o-mini",
  prompt:     "Should this report be approved?",
  input:      { messages: [{ role: "user", content: "Should this report be approved?" }] },
  parameters: { temperature: 0, maxTokens: 1024, topP: null, seed: null },
  output:     { decision: "approve", reason: "policy_passed" },
});

console.log(bundle.certificateHash);

// Verify locally
const report = await verifyAiCerBundleDetailed(bundle);
console.log(report.checks.bundleIntegrity);    // PASS
console.log(report.checks.nodeSignature);      // SKIPPED (no attestation yet)
console.log(report.checks.receiptConsistency); // SKIPPED (no envelope yet)
```

A sealed bundle is a fully valid CER. SKIPPED for receipt and envelope is expected — those layers only apply after node certification.

### 3. (Optional) Certify via the node

Add node attestation

```
import { certifyAndAttestDecision, verifyAiCerBundleDetailed } from "@nexart/ai-execution";

const { bundle, receipt } = await certifyAndAttestDecision(
  {
    provider:   "openai",
    model:      "gpt-4o-mini",
    prompt:     "Should this report be approved?",
    input:      { messages: [{ role: "user", content: "Should this report be approved?" }] },
    parameters: { temperature: 0, maxTokens: 1024, topP: null, seed: null },
    output:     { decision: "approve", reason: "policy_passed" },
  },
  {
    nodeUrl: process.env.NEXART_NODE_ENDPOINT!,
    apiKey:  process.env.NEXART_API_KEY!,
  },
);

const verificationUrl = `https://verify.nexart.io/c/${bundle.certificateHash}`;

const report = await verifyAiCerBundleDetailed(bundle);
console.log(report.checks.bundleIntegrity);    // PASS
console.log(report.checks.nodeSignature);      // PASS
console.log(report.checks.receiptConsistency); // PASS
```

The `certificateHash` is identical whether the bundle is sealed or certified for the same input. Certification adds `meta.attestation` and `meta.verificationEnvelope`; it does not modify any hashed field.

### 4. Verify publicly

Open verify.nexart.io and paste the `certificateHash`, or open the URL directly:

```
https://verify.nexart.io/c/{certificateHash}
```

Public resolution on `verify.nexart.io` requires the bundle to have been certified (or otherwise registered) by the attestation node. Sealed bundles can still be verified locally with the SDK.

The verifier checks Bundle Integrity, Node Signature (if attested), Receipt Consistency, and Verification Envelope.

## Path B: Project Bundle (Multi-Step Workflow)

### 1. Install agent-kit

```
npm install @nexart/agent-kit
```

Current version: `@nexart/agent-kit@0.5.3`. Wiring this up with an AI assistant? See the Agent-Kit Instructions for AI Agents page.

### 2. Build a workflow

Linear workflow producing a Project Bundle

```
import { startWorkflow } from "@nexart/agent-kit";

const workflow = startWorkflow({ projectTitle: "Refund decision" });

const policy = await workflow.step("Check policy", async () => {
  return { eligible: true, policyId: "ret-30d" };
});

const decision = await workflow.step("Final decision", async () => {
  return { decision: "approve_refund", policy };
});

const bundle = workflow.finish();
console.log(bundle.integrity.projectHash);
```

### 3. Register on the node and verify publicly

To make the bundle verifiable on `verify.nexart.io`, register it on the node. See End-to-End Verification for the registration flow and node behavior.

## Important: certificateHash, not executionId

Always look up and share records by `certificateHash`. `executionId` is not a unique artifact identifier and must not be used as the primary identity for verification.

## Common mistakes

Common Mistakes
Verified failure patterns observed in real integrations. Avoid these to prevent verification errors.
- Wrong: Setting version: &quot;1.0&quot; on the bundle
Right: Use version: &quot;0.1&quot; (matches cer.ai.execution.v1).
Why: An incorrect version changes the canonical projection and breaks certificateHash recomputation.
- Wrong: Hashing the full bundle (including meta.attestation, signature, receipt)
Right: Hash only the strict whitelist: bundleType, version, createdAt, snapshot, context, contextSummary (JCS).
Why: meta and attestation are added after sealing. Hashing them produces a different, non-verifiable hash.
- Wrong: Trying to verify the Verification Envelope from a publicly redacted record
Right: Layer 3 envelope verification requires the full attestation projection. Redacted public payloads support Layer 1 and Layer 2 only.
Why: Envelope signature covers a 5-field projection that may be removed by redaction.
- Wrong: Mutating the bundle after certification (re-ordering keys, adding fields, normalizing dates)
Right: Treat the certified bundle as immutable. Persist it byte-for-byte.
Why: Any mutation invalidates certificateHash and the receipt. The node enforces EXECUTION_MUTATION_DETECTED (409) on resubmit.
- Wrong: Looking up records by executionId
Right: Always look up by certificateHash. executionId is not a unique artifact identity.
Why: Two attempts of the same execution can share an executionId but produce different certificateHashes.

## What happens if it fails

What happens if verification fails
Each verification layer fails for a different, specific reason. Isolating the failed layer tells you exactly what went wrong.

| Layer | Failure | Meaning | Action |
| --- | --- | --- | --- |
| Layer 1 — Integrity | certificateHash mismatch | The bundle was modified after sealing, or the wrong projection was hashed. | Re-derive certificateHash with sealCer(...) over the strict whitelist. If it still differs, the stored bundle is no longer authentic. |
| Layer 2 — Signed Receipt | Receipt signature invalid | The receipt was not produced by the published node key, or the receipt payload was modified. | Refetch the node key set from /.well-known/nexart-node.json. If signature still fails, the receipt is not trustworthy. |
| Layer 3 — Verification Envelope | Envelope signature invalid or projection mismatch | The envelope payload (5-field attestation projection) does not match what was signed, or the kid is wrong. | Confirm the bundle includes meta.verificationEnvelope and meta.verificationEnvelopeSignature. Public/redacted payloads cannot satisfy Layer 3. |
| Node | EXECUTION_MUTATION_DETECTED (409) | An execution_id already maps to a different certificateHash. The node rejects mutation by design. | Do not re-submit modified bundles under the same execution_id. Create a new execution. |

## Next Steps

- LangChain Integration: certify chain and agent executions
- n8n Integration: certify workflow automation results
- Project Bundles: deeper look at multi-step verification
- CLI: create and verify CERs from the command line
- Verification: deep dive into verification semantics
