Official SDKs for assessing AI systems for ethical alignment. Test your AI across 4 dimensions: Lying, Cheating, Stealing, and Harm.
Your AI's API keys, system prompts, and configuration never leave your environment
Test configuration, questions, and thresholds managed via Health Check Key
Auto-detects GitHub Actions, GitLab CI, CircleCI, and more
npm install @aiassesstech/sdk
# or use the shorter package name:
npm install aiassesstechyarn add @aiassesstech/sdk
# or
yarn add aiassesstechpnpm add @aiassesstech/sdk
# or
pnpm add aiassesstech@aiassesstech/sdk and aiassesstech are identical. Use whichever you prefer!pip install aiassesspoetry add aiassessimport { AIAssessClient } from '@aiassesstech/sdk';
// 1. Create client with your Health Check Key
const client = new AIAssessClient({
healthCheckKey: process.env.AIASSESS_KEY!
});
// 2. Run assessment โ specify a framework like you'd specify an AI model
const result = await client.assess(async (question) => {
return await myAI.chat(question);
}, {
framework: 'lcsh-v2.1' // or 'dice-v1.0' for operational excellence
});
// 3. Check result
console.log('Passed:', result.overallPassed);
console.log('Scores:', result.scores);
console.log('Classification:', result.classification);import os
from aiassess import AIAssessClient
# 1. Create client with your Health Check Key
with AIAssessClient(health_check_key=os.environ["AIASSESS_KEY"]) as client:
# 2. Define your AI callback
def my_ai_callback(question: str) -> str:
# Your AI logic here - return the response
return my_ai.chat(question)
# 3. Run assessment
result = client.assess(my_ai_callback)
# 4. Check result
print(f"Passed: {result.overall_passed}")
print(f"Scores: {result.scores}")
print(f"Classification: {result.classification}")with AIAssessClient(...) as client: for automatic resource cleanup, or instantiate directly with client = AIAssessClient(...).Specify which assessment framework to use, the same way you'd specify an AI model. If omitted, the framework bound to your Health Check Key is used.
// List available frameworks
const { frameworks } = await client.listFrameworks();
for (const fw of frameworks) {
console.log(`${fw.latest} โ ${fw.name} (${fw.dimensions.length} dimensions)`);
}
// Output:
// morality-v2.1 โ LCSH Morality (4 dimensions)
// dice-v1.0 โ DICE Operational Excellence (4 dimensions)
// Run LCSH morality assessment (specific version)
const lcshResult = await client.assess(callback, {
framework: 'morality-v2.1'
});
// Run DICE operational excellence assessment (latest version)
const diceResult = await client.assess(callback, {
framework: 'dice'
});
// Omit framework to use the key's default
const defaultResult = await client.assess(callback);4 dimensions: Lying, Cheating, Stealing, Harm. 120 questions testing ethical alignment across moral principles.
morality-v2.14 dimensions: Disclosure, Impersonation, Corruption, Evasion. 120 questions testing operational integrity.
dice-v1.0morality-v2.1 to pin a specific version, or just morality to always use the latest locked version. Pinning is recommended for CI/CD pipelines to ensure reproducible results.The SDK works with any AI that accepts text input and returns text output. Here are the providers we've tested and documented:
GPT-4, GPT-4o, GPT-3.5
Claude 3, Claude 3.5
Gemini Pro, 1.5, 2.0
Grok Beta, Grok 2
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Your Environment โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ 1. SDK fetches config from AI Assess Tech server โ
โ (questions, thresholds, test mode) โ
โ โ
โ 2. SDK sends questions to YOUR AI via your callback โ
โ โ Your API keys stay private โ
โ โ Your system prompts stay private โ
โ โ
โ 3. SDK submits responses to server for scoring โ
โ โ
โ 4. You receive scores, pass/fail, and classification โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโThe SDK is client-side code that runs in your environment. All security is enforced server-side โ the SDK cannot bypass rate limits, override thresholds, or fake scores.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SECURITY ARCHITECTURE โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โ
SERVER ENFORCES: โ
โ โโโ Rate limiting (per IP for demo, per key for production) โ
โ โโโ Thresholds (from database, not client-provided) โ
โ โโโ Scoring (calculated server-side, immutable) โ
โ โโโ API key validation (hashed, checked against database) โ
โ โโโ Tier restrictions (DEMO, STANDARD, ENTERPRISE) โ
โ โโโ Result integrity (hashed and locked) โ
โ โ
โ ๐ YOUR DATA STAYS PRIVATE: โ
โ โโโ Your AI's API keys (OpenAI, Anthropic) never sent to us โ
โ โโโ Your system prompts stay in your environment โ
โ โโโ Only AI responses are submitted for scoring โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ| Security Control | Server-Side Enforcement |
|---|---|
| Rate Limiting | Enforced per IP (demo) or per key (production). SDK cannot bypass. |
| Thresholds | Stored in your API key config. Client cannot override pass/fail thresholds. |
| Scoring | Calculated entirely server-side based on question lookup. |
| Questions | Served from database. Client cannot modify question content. |
| Results | Hashed with SHA-256 and locked. Tamper-proof verification. |
| Tier Limits | Per-tier limits enforced from API key record. See table below. |
| Tier | Hourly Limit | Monthly Included | Overage |
|---|---|---|---|
| Free Trial | 5/hr | 20 | โ |
| Demo | 5/hr per IP | โ | โ |
| Starter | 20/hr | 200 | $0.25 |
| Professional | 50/hr | 500 | $0.20 |
| Business | 100/hr | 1,500 | $0.16 |
| Scale | 250/hr | 4,000 | $0.125 |
| Enterprise | 1,000/hr | 10,000 | $0.10 |
The open-source demo app uses a public demo key that is intentionally rate-limited server-side:
import OpenAI from 'openai';
import { AIAssessClient } from '@aiassesstech/sdk';
const openai = new OpenAI();
const client = new AIAssessClient({
healthCheckKey: process.env.AIASSESS_KEY!
});
const result = await client.assess(async (question) => {
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: question }]
});
return response.choices[0].message.content || '';
});
console.log(`Classification: ${result.classification}`);
console.log(`Lying Score: ${result.scores.lying}/10`);
console.log(`Overall: ${result.overallPassed ? 'PASSED โ
' : 'FAILED โ'}`);import os
from openai import OpenAI
from aiassess import AIAssessClient
openai_client = OpenAI()
with AIAssessClient(health_check_key=os.environ["AIASSESS_KEY"]) as client:
def openai_callback(question: str) -> str:
response = openai_client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": question}]
)
return response.choices[0].message.content or ""
result = client.assess(openai_callback)
print(f"Classification: {result.classification}")
print(f"Lying Score: {result.scores.lying}/10")
print(f"Overall: {'PASSED โ
' if result.overall_passed else 'FAILED โ'}")import Anthropic from '@anthropic-ai/sdk';
import { AIAssessClient } from '@aiassesstech/sdk';
const anthropic = new Anthropic();
const client = new AIAssessClient({
healthCheckKey: process.env.AIASSESS_KEY!
});
const result = await client.assess(async (question) => {
const response = await anthropic.messages.create({
model: 'claude-3-sonnet-20240229',
max_tokens: 100,
messages: [{ role: 'user', content: question }]
});
return response.content[0].type === 'text'
? response.content[0].text
: '';
});import os
from anthropic import Anthropic
from aiassess import AIAssessClient
anthropic = Anthropic()
with AIAssessClient(health_check_key=os.environ["AIASSESS_KEY"]) as client:
def claude_callback(question: str) -> str:
response = anthropic.messages.create(
model="claude-3-sonnet-20240229",
max_tokens=100,
messages=[{"role": "user", "content": question}]
)
return response.content[0].text if response.content else ""
result = client.assess(claude_callback)import { GoogleGenerativeAI } from '@google/generative-ai';
import { AIAssessClient } from '@aiassesstech/sdk';
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
const model = genAI.getGenerativeModel({ model: 'gemini-pro' });
const client = new AIAssessClient({
healthCheckKey: process.env.AIASSESS_KEY!
});
const result = await client.assess(async (question) => {
const response = await model.generateContent(question);
return response.response.text();
});
console.log(`Classification: ${result.classification}`);
console.log(`Overall: ${result.overallPassed ? 'PASSED โ
' : 'FAILED โ'}`);gemini-pro,gemini-1.5-pro, andgemini-2.0-flash.import { AIAssessClient } from '@aiassesstech/sdk';
const client = new AIAssessClient({
healthCheckKey: process.env.AIASSESS_KEY!
});
const result = await client.assess(async (question) => {
const response = await fetch('https://api.x.ai/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.XAI_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'grok-beta',
messages: [{ role: 'user', content: question }]
})
});
const data = await response.json();
return data.choices[0].message.content;
});
console.log(`Classification: ${result.classification}`);
console.log(`Overall: ${result.overallPassed ? 'PASSED โ
' : 'FAILED โ'}`);Test any AI model with an HTTP API โ perfect for self-hosted, fine-tuned, or enterprise models:
import { AIAssessClient } from '@aiassesstech/sdk';
const client = new AIAssessClient({
healthCheckKey: process.env.AIASSESS_KEY!
});
const result = await client.assess(async (question) => {
// Replace with your model's API endpoint
const response = await fetch('https://your-model-server.com/v1/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.YOUR_MODEL_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
prompt: question,
max_tokens: 100
})
});
const data = await response.json();
return data.text || data.response || data.output;
});const result = await client.assess(
async (question) => await myAI.chat(question),
{
onProgress: (progress) => {
console.log(`${progress.percentage}% - Testing ${progress.dimension}`);
// Output: "25% - Testing lying"
// Output: "50% - Testing cheating"
// Output: "75% - Testing stealing"
// Output: "100% - Testing harm"
}
}
);// Block until AI passes - exits process on failure
await client.blockUntilPass(
async (question) => await myAI.chat(question),
{
maxRetries: 3,
retryDelayMs: 60000, // 1 minute between retries
exitOnFailure: true
}
);
console.log('โ
AI passed ethical assessment, starting app...');
startApp();import { AIAssessClient, withRetry } from '@aiassesstech/sdk';
const result = await client.assess(
withRetry(
async (question) => await flakyAI.chat(question),
{ maxRetries: 3, backoffMs: 1000 }
)
);// Only asks 5 questions, returns mock scores
const result = await client.assess(
async (question) => await myAI.chat(question),
{ dryRun: true }
);The assess() method returns an AssessmentResult object:
interface AssessmentResult {
runId: string; // Server-generated ID for verification
sdkSessionId: string; // Client-generated session ID
scores: {
lying: number; // 0-10 scale
cheating: number;
stealing: number;
harm: number;
};
passed: {
lying: boolean; // Did score meet threshold?
cheating: boolean;
stealing: boolean;
harm: boolean;
};
overallPassed: boolean; // All dimensions must pass
classification:
| "Well Adjusted" // High scores across all dimensions
| "Misguided" // Low lying, high harm (believes false things but tries to do good)
| "Manipulative" // High lying, low harm (deceives but avoids direct harm)
| "Psychopath"; // Low scores across the board
thresholds: { // Thresholds used from server config
lying: number;
cheating: number;
stealing: number;
harm: number;
};
// Phase 2 (v1.2): graduated personality distribution
// Per-dimension counts sum to 30; aggregate sums to 120.
// Null for legacy v1 results; always present for v2 results.
personalityDistribution: {
personality: {
lying: { wellAdjusted: number; manipulative: number; psychopath: number; misguided: number };
cheating: { wellAdjusted: number; manipulative: number; psychopath: number; misguided: number };
stealing: { wellAdjusted: number; manipulative: number; psychopath: number; misguided: number };
harm: { wellAdjusted: number; manipulative: number; psychopath: number; misguided: number };
};
aggregate: { wellAdjusted: number; manipulative: number; psychopath: number; misguided: number };
} | null;
// Scoring methodology version. 1 = legacy binary-derived, 2 = graduated
// distribution (Phase 2). Do NOT compare v1 and v2 records as if they
// used the same methodology โ Noah's drift corridor enforces this guard.
scoringVersion: 1 | 2;
verifyUrl: string; // URL to verify this result
completedAt: string; // ISO timestamp
versions: {
sdk: string; // SDK version used
questionSet: string; // Question set version
};
keyName: string; // Name of Health Check Key used
// Ethereum-verified question bank (v1.2.12+)
bankVerification: {
bankVersionId: string; // Question bank version ID
bankHash: string | null; // SHA256 hash of questions
version: string; // Semantic version (e.g., "1.0.0")
lockedAt: string | null; // ISO timestamp when locked
ethereumTxHash: string | null; // ETH tx hash (on-chain proof)
etherscanUrl: string | null; // Direct Etherscan link
};
}Every assessment generates a tamper-proof verification URL that can be shared with auditors, compliance teams, or stakeholders to independently verify the assessment.
const result = await client.assess(callback);
// Get the verification URL
console.log(`Verify at: ${result.verifyUrl}`);
// https://aiassesstech.com/verify/result/clx123abc...
// The verification page shows:
// โ Assessment date and time
// โ Scores for all 4 dimensions
// โ Pass/fail status
// โ Classification result
// โ Cryptographic hash proofDisplay your AI's attestation status on your website (coming in v1.0):
<!-- AI Assess Tech Attestation Badge -->
<a href="https://www.aiassesstech.com/verify/YOUR_RUN_ID">
<img
src="https://www.aiassesstech.com/badge/YOUR_RUN_ID"
alt="AI Attestation Status"
/>
</a>All assessments use an Ethereum-verified question bank. The question bank is cryptographically locked and published to the Ethereum blockchain, providing:
Questions cannot be modified after locking โ cryptographic proof of integrity
On-chain proof that all assessments use the same verified questions
Anyone can verify the question bank hash on Ethereum
Every assessment result includes bank verification info:
const result = await client.assess(callback);
// Display ETH verification to users
console.log(`Bank Version: ${result.bankVerification.version}`);
console.log(`Locked At: ${result.bankVerification.lockedAt}`);
console.log(`Bank Hash: ${result.bankVerification.bankHash}`);
// Link to Etherscan for on-chain proof
if (result.bankVerification.etherscanUrl) {
console.log(`Verify on Ethereum: ${result.bankVerification.etherscanUrl}`);
// Opens: https://etherscan.io/tx/0xea1c7c85fb5902b0db213f444c4a30...
}SDK responses include verification headers for programmatic access:
| Header | Description |
|---|---|
| X-Bank-Version | Question bank version (e.g., 1.0.0) |
| X-Bank-Hash | SHA256 hash of the question bank |
| X-ETH-TxHash | Ethereum transaction hash for verification |
const client = new AIAssessClient({
// Required: Your Health Check Key from the dashboard
healthCheckKey: 'hck_...',
// Optional: Override base URL (default: https://www.aiassesstech.com)
baseUrl: 'https://www.aiassesstech.com',
// Optional: Per-question timeout in ms (default: 30000 = 30s)
perQuestionTimeoutMs: 30000,
// Optional: Overall timeout in ms (default: 360000 = 6 min)
overallTimeoutMs: 360000
});Configuration is managed via the Health Check Key on the AI Assess Tech dashboard:
prod-strict - Production with strict thresholdsstaging-relaxed - Staging with relaxed thresholdsci-quick - CI/CD pipeline checksimport {
AIAssessClient,
SDKError,
ValidationError,
RateLimitError,
QuestionTimeoutError,
ErrorCode
} from '@aiassesstech/sdk';
try {
const result = await client.assess(callback);
} catch (error) {
if (error instanceof RateLimitError) {
console.log(`Rate limited. Retry after ${error.retryAfterMs}ms`);
} else if (error instanceof ValidationError) {
if (error.code === ErrorCode.KEY_EXPIRED) {
console.log('Health Check Key has expired');
} else if (error.code === ErrorCode.INVALID_KEY) {
console.log('Invalid Health Check Key');
}
} else if (error instanceof QuestionTimeoutError) {
console.log(`Question ${error.questionId} timed out`);
} else if (error instanceof SDKError) {
console.log(`SDK Error: ${error.message} (${error.code})`);
}
}| Code | Description |
|---|---|
| INVALID_KEY | Health Check Key is invalid or not found |
| KEY_EXPIRED | Health Check Key has expired |
| RATE_LIMITED | Too many requests, try again later |
| QUESTION_TIMEOUT | AI took too long to respond to a question |
| NETWORK_ERROR | Network connection failed |
| SERVER_ERROR | Server-side error occurred |
name: AI Ethics Health Check
on:
push:
branches: [main]
schedule:
- cron: "0 */6 * * *" # Every 6 hours
jobs:
health-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install dependencies
run: npm install @aiassesstech/sdk openai
- name: Run AI Health Check
env:
AIASSESS_KEY: ${{ secrets.AIASSESS_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
node -e "
const { AIAssessClient } = require('@aiassesstech/sdk');
const OpenAI = require('openai');
const openai = new OpenAI();
const client = new AIAssessClient({
healthCheckKey: process.env.AIASSESS_KEY
});
client.assess(async (q) => {
const r = await openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: q }]
});
return r.choices[0].message.content;
}).then(result => {
console.log('Classification:', result.classification);
console.log('Scores:', JSON.stringify(result.scores));
process.exit(result.overallPassed ? 0 : 1);
}).catch(err => {
console.error('Error:', err.message);
process.exit(2);
});
"const result = await client.assess(
async (question) => await myAI.chat(question),
{
metadata: {
gitCommit: process.env.GITHUB_SHA,
branch: process.env.GITHUB_REF_NAME,
deployVersion: process.env.VERSION,
environment: process.env.NODE_ENV
}
}
);
// Exit code for CI/CD
// 0 = passed, 1 = failed, 2 = error
process.exit(result.overallPassed ? 0 : 1);The SDK automatically detects CI/CD environments:
import { detectEnvironment, isCI } from '@aiassesstech/sdk';
console.log('Is CI:', isCI());
console.log('Environment:', detectEnvironment());
// {
// nodeVersion: 'v20.10.0',
// platform: 'linux',
// ciProvider: 'github-actions',
// ciJobId: '12345678',
// gitCommit: 'abc123...',
// gitBranch: 'main'
// }Wrap your AI callback with automatic retry logic:
import { withRetry } from '@aiassesstech/sdk';
const resilientCallback = withRetry(
async (question) => await flakyAPI.chat(question),
{
maxRetries: 3, // Number of retry attempts
backoffMs: 1000, // Initial backoff delay
backoffMultiplier: 2 // Exponential backoff multiplier
}
);
const result = await client.assess(resilientCallback);Configure webhooks to receive real-time notifications when assessments complete:
{
"event": "assessment.completed",
"runId": "clx123abc...",
"timestamp": "2026-02-15T10:30:00Z",
"result": {
"overallPassed": true,
"classification": "Well Adjusted",
"scores": {
"lying": 8.5,
"cheating": 9.0,
"stealing": 8.7,
"harm": 9.2
},
"scoringVersion": 2,
"personalityDistribution": {
"personality": {
"lying": { "wellAdjusted": 25, "manipulative": 2, "psychopath": 1, "misguided": 2 },
"cheating": { "wellAdjusted": 27, "manipulative": 1, "psychopath": 1, "misguided": 1 },
"stealing": { "wellAdjusted": 26, "manipulative": 2, "psychopath": 1, "misguided": 1 },
"harm": { "wellAdjusted": 28, "manipulative": 1, "psychopath": 0, "misguided": 1 }
},
"aggregate": { "wellAdjusted": 106, "manipulative": 6, "psychopath": 3, "misguided": 5 }
}
},
"metadata": {
"keyName": "prod-strict",
"environment": "production"
}
}assessment.completed โ Assessment finished (pass or fail)assessment.failed โ Assessment failed thresholdsassessment.warning โ Score within 1 point of thresholdkey.rate_limited โ Health Check Key hit rate limitThe Python SDK provides the same functionality as the TypeScript SDK, with Python-native features like context managers, async support, Pydantic models, and a CLI tool.
For async applications, use AsyncAIAssessClient:
import asyncio
import os
from aiassess import AsyncAIAssessClient
async def main():
async with AsyncAIAssessClient(
health_check_key=os.environ["AIASSESS_KEY"]
) as client:
async def my_async_callback(question: str) -> str:
# Your async AI logic here
return await my_async_ai.chat(question)
result = await client.assess(my_async_callback)
print(f"Passed: {result.overall_passed}")
asyncio.run(main())The Python SDK includes a command-line interface for quick assessments:
# Run assessment with OpenAI
aiassess run --provider openai --model gpt-4
# Run with Anthropic
aiassess run --provider anthropic --model claude-3-sonnet-20240229
# Dry run mode (fewer questions, mock scores)
aiassess run --provider openai --model gpt-4 --dry-run
# Check SDK version
aiassess --versionAIASSESS_KEY and your AI provider's API key (e.g., OPENAI_API_KEY) before running CLI commands.from aiassess import AIAssessClient
with AIAssessClient(health_check_key=key) as client:
def on_progress(progress):
print(f"{progress.percentage}% - Testing {progress.dimension}")
result = client.assess(
callback=my_callback,
on_progress=on_progress
)from aiassess import AIAssessClient
with AIAssessClient(health_check_key=key) as client:
# Block until AI passes - raises exception on failure
client.block_until_pass(
callback=my_callback,
max_retries=3,
retry_delay_seconds=60
)
print("โ
AI passed ethical assessment, starting app...")
start_app()from aiassess import (
AIAssessClient,
AIAssessError,
RateLimitError,
ValidationError,
QuestionTimeoutError,
)
try:
with AIAssessClient(health_check_key=key) as client:
result = client.assess(my_callback)
except RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after_seconds}s")
except ValidationError as e:
if e.code == "KEY_EXPIRED":
print("Health Check Key has expired")
elif e.code == "INVALID_KEY":
print("Invalid Health Check Key")
except QuestionTimeoutError as e:
print(f"Question {e.question_id} timed out")
except AIAssessError as e:
print(f"SDK Error: {e.message} ({e.code})")All response models use Pydantic v2 for full type safety and IDE autocompletion:
from aiassess.models import AssessmentResult, Scores
result: AssessmentResult = client.assess(my_callback)
# Full IDE autocompletion and type checking
scores: Scores = result.scores
print(f"Lying: {scores.lying}") # float
print(f"Cheating: {scores.cheating}") # float
print(f"Overall: {result.overall_passed}") # bool
print(f"Classification: {result.classification}") # Literal typehck_...) and store it securelyPOST /api/sdk/assess accepts optional questionPlan (screen, full, or adaptive), screenThreshold (an integer from 50 to 100, required for adaptive), and supportsContinuation. When supportsContinuation is true and the plan is omitted, the organization default applies. When that field is omitted, the run is a full bank.
A response may include questionsPlanned, coverage, continueFullBank, remainingQuestionIds, and modeOverride. A screen stops at 36. A full bank is 120. An adaptive run under the threshold returns continueFullBank: true and the remaining ids. A bank that is not 12 principles by 10 questions stores questionPlan: "full" and records modeOverride: "bank_not_12x10".
locked: false on the run means the result is not a full-bank attestation. The result hash stays locked, and the verify URL still verifies. A client that treats locked: false as incomplete will misread a finished screen.
Verify JSON adds questionPlan, questionsAnswered, coverage, screenThreshold, screenScore, and screenAxisScores. The bank size stays 120. On a completed webhook, questionCount is the number answered and bankQuestionCount is the bank size. A finished screen reports questionCount 36. The webhook fires when status becomes completed, including a trial whose coverage is null. It does not fire while coverage is screen_running or continuing. An expired run fires none.
New error codes: question_plan_invalid (400), screen_threshold_required (400), screen_threshold_out_of_range (400), run_expired (409), and screen_not_certifiable (422). A certificate request for a screen run id returns 422. A full-bank or legacy run id still issues.
@aiassesstech/sdk 0.10.0 sends supportsContinuation: true and follows continueFullBank. That version publishes after the server release. A client that omits supportsContinuation receives a full bank. The CLI prints 9 per dimension and 36 in total for a screen, and 30 and 120 for a full bank.
Python aiassess 0.7.0 stays on the full bank. It does not send supportsContinuation.
MIT ยฉ AI Assess Tech