API Documentation Chainlink Confidential AI Attester Demo
This document describes the REST API for the Confidential AI Attester Demo — a confidential AI inference pipeline that runs inside an AWS Nitro Enclave.
Disclaimer
This project is being made available for experimentation by participants of the ETHGlobal New York 2026 Hackathon and is intended solely for demonstration and educational purposes. This project represents an example of using a Chainlink product or service and is provided to help you understand how to interact with Chainlink's systems and services so that you can integrate them into your own. This is provided "AS IS" and "AS AVAILABLE" without warranties of any kind, has not been audited, and may be missing key checks or error handling to make the usage of the product more clear. Do not use any code in this project in a production environment without completing your own audits and application of best practices. Neither Chainlink Labs, the Chainlink Foundation, nor Chainlink node operators are responsible for unintended outputs that are generated due to errors in code.
Privacy notice
For development purposes in the context of this demo, submitted requests and their resources (prompts, fetched/uploaded documents, and callback URLs) may be logged. Credentials — such as passed authorization headers or cookies on resource requests — are never logged.
Research Context
The motivation for confidential inference here echoes πCreds: Privately Inferred Credentials: much valuable data is private and unstructured, and TEEs let an LLM reason over it while revealing only a bounded result. That paper composes a TEE-based oracle that fetches authenticated data with private LLM inference inside a single protected boundary, and traces the oracle concept back to blockchain data feeds — a lineage this project mirrors through its optional CRE callback that delivers results to a downstream consumer. This API demonstrates the first steps of that pattern: submit a prompt and resources, process them in an enclave, retrieve the output, and trigger a (CRE) callback.
Overview
The system accepts prompts and optional resources (HTTP(S) URLs or uploaded files), prepares them inside the enclave, runs inference, and returns the model output. Requests are processed asynchronously: submit a request, then poll for the result.
Deployment
Base URL: https://confidential-ai-dev-preview.cldev.cloud/
Public endpoints
| Endpoint | Description |
|---|---|
| /v1/models | List supported models |
| /v1/inference | Submit an inference request |
| /v1/inference/:id | Poll inference status and results |
| /v1/fetch | Test a resource fetch |
Supported models
| Model ID | Name | Size | Context | Capabilities |
|---|---|---|---|---|
gemma4 |
Gemma 4 | 9.6GB | 131,072 tokens | text, image |
qwen3.6 |
Qwen 3.6 | 34.4GB | 262,144 tokens | text, image |
Key Concepts
Asynchronous inference
Inference is not synchronous. Submit a request with POST /v1/inference and receive 202 Accepted with an initial snapshot. Poll GET /v1/inference/:id until the status reaches a terminal state (completed or failed).
Resources
Each inference request may include up to 10 resources, either:
- Fetched URL — the server downloads the resource over HTTP(S) using the specified method, headers, and body.
- Uploaded file — the client sends base64-encoded content with a filename and content type.
Resources are converted to Markdown via Docling when appropriate (PDF, Office documents, etc.). Images can be passed directly to vision-capable models. Each resource is capped at 10 MiB.
Upstream credentials (e.g. Authorization headers) are forwarded at fetch time but not stored.
Request ownership
Each inference request is bound to the API key that submitted it. Users can only read their own requests. Missing requests and unauthorized access both return 404 to avoid leaking request existence.
CRE callback
An optional cre_callback registers a single HTTP(S) URL. When the request reaches a terminal state, the server POSTs the status response wrapped as {"input": <status response>} to that URL. Delivery is best-effort with a 10-second timeout and no retries.
Scheduling
Requests are scheduled fairly across API keys rather than strictly first-come, first-served. The next request to run is taken from the API key that has waited longest — the key whose most recent request started the longest ago, with keys that have not run yet served first. Ties are broken by submission time.
Within a single API key, that key's requests run in submission order (FIFO).
In effect this is round-robin by API key: when several keys have requests waiting, the server cycles through the keys one request at a time, so a single key submitting a large batch cannot starve the others. While a request is still queued, the queue_position field in its status response reflects its place in this fair order.
Queue limits
| Limit | Default | Error when exceeded |
|---|---|---|
| Global pending queue | 100 | 503 queue_full |
| Per-key pending queue | 10 | 429 per_key_limit |
| Result retention | 30 minutes | Request expires (returns 404) |
REST API
Authentication
All /v1/* endpoints require a Bearer API key:
Authorization: Bearer <api-key>Obtaining an API key
API keys are distributed to Hackathon participants out of band. Treat your key as a secret and send it only over HTTPS.
Endpoints
GET /v1/models
List supported models.
Authentication: Required (Bearer API key).
Response (200 OK):
{
"models": [
{
"id": "gemma4",
"name": "Gemma 4",
"size": "9.6GB",
"context": 131072,
"capabilities": ["text", "image"],
"backend": "local"
},
{
"id": "qwen3.6",
"name": "Qwen 3.6",
"size": "34.4GB",
"context": 262144,
"capabilities": ["text", "image"],
"backend": "local"
}
]
}POST /v1/inference
Submit an inference request. Returns 202 Accepted with the initial request snapshot.
Authentication: Required (Bearer API key).
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
model |
String | Yes | Model ID |
prompt |
String | Yes | User prompt |
system_prompt |
String | No | Override the default system prompt |
resources |
Array | No | Up to 10 resources (see Resources) |
cre_callback |
Object | No | Optional callback on terminal state |
Resource object (fetched URL):
| Field | Type | Required | Description |
|---|---|---|---|
url |
String | Yes | HTTP or HTTPS URL |
method |
String | No | HTTP method (default: GET) |
headers |
Object | No | Request headers (e.g. upstream auth) |
body |
String | No | Request body |
encoding |
String | No | Body encoding: text (default) or hex |
preprocess |
Boolean | No | Force Docling conversion on/off (default: auto-detect) |
Resource object (uploaded file):
| Field | Type | Required | Description |
|---|---|---|---|
filename |
String | No | Original filename (defaults to upload.bin if omitted) |
content_type |
String | No | MIME type (auto-detected if omitted) |
content_base64 |
String | Yes | Base64-encoded file content |
preprocess |
Boolean | No | Force Docling conversion on/off |
CRE callback object:
| Field | Type | Required | Description |
|---|---|---|---|
url |
String | Yes | HTTP or HTTPS callback URL |
Request example (URL resource):
{
"model": "gemma4",
"prompt": "Summarize the attached report and list the key risks.",
"resources": [
{
"url": "https://example.com/report.pdf",
"method": "GET",
"headers": {
"Authorization": "Bearer <upstream-token>"
}
}
]
}Request example (uploaded file):
{
"model": "gemma4",
"prompt": "Extract the relevant obligations from this document.",
"resources": [
{
"filename": "contract.txt",
"content_type": "text/plain",
"content_base64": "Q29udHJhY3QgdGV4dCBnb2VzIGhlcmU="
}
]
}Request example (with CRE callback):
{
"model": "gemma4",
"prompt": "Summarize the attached report.",
"cre_callback": {
"url": "https://your-cre-http-trigger.example/endpoint"
}
}Response (202 Accepted):
{
"id": "0198a69b-2c30-7f12-a411-7d86de7c4a01",
"status": "queued",
"queue_position": 1,
"model": "gemma4",
"system_prompt": "You are a helpful assistant. When documents are provided, base your answers on their content. If the documents do not contain enough information to answer, say so.",
"prompt": "Summarize the attached report and list the key risks.",
"resource_summaries": [
{
"filename": "report.pdf",
"url": "https://example.com/report.pdf"
}
],
"created_at": "2026-05-29T12:00:00Z",
"status_at": "2026-05-29T12:00:00Z"
}Empty/zero-valued fields are omitted from responses. In the initial
202snapshot the resource has not been fetched yet, sodigest,content_type,size, andpreprocessedare absent from eachresource_summariesentry; they appear once the resource has been fetched and processed.
Status values:
| Status | Meaning |
|---|---|
queued |
Waiting in the pipeline |
preparing-resources |
Fetching and preprocessing resources |
processing |
Running model inference |
completed |
Finished successfully |
failed |
Failed with an error |
Errors:
| Status | Error | Meaning |
|---|---|---|
400 |
invalid_request |
Bad JSON, missing fields, invalid callback URL, or more than 10 resources |
400 |
unsupported_model |
Model ID is not supported |
429 |
per_key_limit |
Too many pending requests for this API key |
503 |
queue_full |
Global pending queue is full |
503 |
maintenance_mode |
Maintenance mode is enabled |
Validation timing: Only
model,prompt, model support, the resource count (≤ 10), andcre_callback.urlsyntax are validated synchronously and can return400. Per-resource problems — specifying bothurlandcontent_base64(or neither), invalid base64, exceeding the 10 MiB cap, or a failed URL fetch — are detected asynchronously while the request is processed: the request still returns202, then reachesstatus: "failed"with details inerror. Always poll until a terminal state instead of relying solely on the submit response.
GET /v1/inference/:id
Return the current state of a previously submitted request. Poll this endpoint until status is completed or failed.
Authentication: Required (Bearer API key). Users can only read their own requests.
Path parameter: id — the inference request ID returned by POST /v1/inference.
Response (200 OK, completed example):
{
"id": "0198a69b-2c30-7f12-a411-7d86de7c4a01",
"status": "completed",
"model": "gemma4",
"system_prompt": "You are a helpful assistant. When documents are provided, base your answers on their content. If the documents do not contain enough information to answer, say so.",
"prompt": "Summarize the attached report and list the key risks.",
"resource_summaries": [
{
"filename": "report.pdf",
"url": "https://example.com/report.pdf",
"digest": "<sha256-of-original-content>",
"content_type": "application/pdf",
"size": 123456,
"preprocessed": true
}
],
"output": "The report identifies three primary risks...",
"usage": {
"prompt_tokens": 1200,
"completion_tokens": 180
},
"resources": [
{
"url": "https://example.com/report.pdf",
"digest": "<sha256-of-original-content>",
"request_digest": "<sha256-of-canonical-request-metadata>",
"response_digest": "<sha256-of-canonical-response-metadata>",
"content_type": "application/pdf",
"preprocessed": true,
"filename_digest": "<sha256-of-blinded-filename>",
"filename_blinding": "<hex-random-blinding-value>"
}
],
"cre_callback": {
"url": "https://your-cre-http-trigger.example/endpoint",
"executed": true,
"success": true,
"status_code": 200,
"executed_at": "2026-05-29T12:00:16Z"
},
"created_at": "2026-05-29T12:00:00Z",
"started_at": "2026-05-29T12:00:01Z",
"status_at": "2026-05-29T12:00:42Z",
"completed_at": "2026-05-29T12:00:15Z"
}Response fields:
| Field | Description |
|---|---|
queue_position |
Position in queue (only while status is queued) |
output |
Model output text (when completed) |
error |
Error message (when failed) |
usage |
Token counts (prompt_tokens, completion_tokens) |
resource_summaries |
Lightweight resource metadata |
resources |
Full resource digests for verification (when processing completes) |
cre_callback |
Callback URL and delivery outcome |
status_at |
Server time when this snapshot was generated |
Errors:
| Status | Error | Meaning |
|---|---|---|
400 |
invalid_id |
ID is not a valid UUID |
404 |
not_found |
Request not found, expired, or not owned by caller |
POST /v1/fetch
Server-side resource fetch used by the playground. Returns the raw upstream response body with metadata headers. Requires a Bearer API key, like other /v1/* endpoints.
Authentication: Required (Bearer API key).
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
url |
String | Yes | HTTP or HTTPS URL |
method |
String | No | HTTP method (default: GET) |
headers |
Object | No | Request headers |
body |
String | No | Request body |
encoding |
String | No | Body encoding: text (default) or hex |
Request example:
curl -sS "$BASE_URL/v1/fetch" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/report.pdf"}' \
-o report.pdf -D -Response (200 OK): Raw upstream body with headers:
| Header | Description |
|---|---|
X-Fetch-Size |
Response body size in bytes |
X-Fetch-Sha256 |
SHA-256 digest of response body |
X-Fetch-Filename |
Resolved filename |
X-Fetch-Content-Type |
Content type |
Errors:
| Status | Body | Meaning |
|---|---|---|
400 |
{"error":"..."} |
Invalid request or missing URL |
502 |
{"error":"..."} |
Upstream fetch failed |
Web UI
These pages load without authentication, but API calls made from them still require a Bearer API key.
| Path | Purpose | Notes |
|---|---|---|
/ |
Status landing page | Public |
/playground |
Inference UI | Enter your API key in the page |
/docs |
API documentation | Public |
Error Responses
Most errors return a JSON object:
{
"error": "error_code",
"message": "Human-readable description"
}Authentication errors:
| Status | Error | Description |
|---|---|---|
401 |
missing_api_key |
No Authorization header |
401 |
invalid_authorization |
Header is not Bearer <key> |
401 |
invalid_api_key |
Malformed or invalid API key |
403 |
banned_api_key |
API key has been revoked |
Inference errors (see endpoint sections above):
| Status | Error | Description |
|---|---|---|
400 |
invalid_request |
Bad JSON or validation failure |
400 |
unsupported_model |
Unknown model ID |
400 |
invalid_id |
Invalid UUID in path |
404 |
not_found |
Request not found or unauthorized |
429 |
per_key_limit |
Per-key queue limit reached |
503 |
queue_full |
Global queue full |
503 |
maintenance_mode |
Maintenance mode active |
Inference Flow Summary
- List models:
GET /v1/modelsto see available models. - Submit:
POST /v1/inferencewith model, prompt, and optional resources. - Poll:
GET /v1/inference/:iduntil status iscompletedorfailed. - Read output: The
outputfield contains the model response when completed. - Optional callback: If
cre_callbackwas set, check its delivery state in the response.
Example with curl:
export BASE_URL=https://confidential-ai-dev-preview.cldev.cloud
export API_KEY=<your-api-key>
# Submit
RESP=$(curl -sS "$BASE_URL/v1/inference" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gemma4","prompt":"Hello, world!"}')
echo "$RESP"
# Poll (replace ID from response)
curl -sS "$BASE_URL/v1/inference/<id>" \
-H "Authorization: Bearer $API_KEY"