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:

  1. Fetched URL — the server downloads the resource over HTTP(S) using the specified method, headers, and body.
  2. 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 202 snapshot the resource has not been fetched yet, so digest, content_type, size, and preprocessed are absent from each resource_summaries entry; 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), and cre_callback.url syntax are validated synchronously and can return 400. Per-resource problems — specifying both url and content_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 returns 202, then reaches status: "failed" with details in error. 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

  1. List models: GET /v1/models to see available models.
  2. Submit: POST /v1/inference with model, prompt, and optional resources.
  3. Poll: GET /v1/inference/:id until status is completed or failed.
  4. Read output: The output field contains the model response when completed.
  5. Optional callback: If cre_callback was 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"