# AiCred Verified API v1

Status: `active`  
Audience: partner engineers, AI coding agents, integration tooling  
Base URL: `https://aicred.ai`

This document is the machine-first integration spec for AiCred Verified API v1.

## Agent implementation checklist

Execute these steps in order:

1. store `AICRED_API_KEY` as a server-side environment variable
2. store the registered AiCred `partner` UUID and exact `callback_url`
3. build an authorize URL to `GET /authorize` with `partner`, `callback_url`, and a partner-generated `state`
4. persist `state` before redirecting the user
5. on callback, validate `state`
6. if `error=denied`, stop cleanly and do not retry automatically
7. if `code` exists, call `POST /functions/v1/api-token-exchange`
8. persist the returned `credential_token` against the connected partner-side user
9. fetch the credential server-side with `GET /functions/v1/api-credential`
10. if `200`, render the official card or map the canonical payload into your own UI
11. if `403`, mark the connection as revoked and stop showing stale credential data
12. if `404`, render an in-progress or unavailable state
13. never expose `AICRED_API_KEY` in browser code
14. treat `credential.meta.cache_ttl` as the maximum cache window

## Purpose

AiCred Verified lets a partner platform:

1. redirect a signed-in partner user to AiCred for explicit human consent
2. exchange the returned authorization code for a persistent `credential_token`
3. fetch a read-only verified credential payload for display or ranking, including overall score, component scores, and per-section scores with summaries

The credential is based on the user's completed conversational evaluation, not self-reported claims.

## Security model

- API keys are partner-scoped and generated by AiCred
- API keys are sent in `X-AiCred-API-Key`
- credential tokens are opaque and sent in `X-Credential-Token`
- auth codes are single-use and expire after `10 minutes`
- callback URL must match the registered partner callback exactly
- callback URL must use `https`
- CORS is restricted to the registered partner domain for browser requests
- users can revoke access later; revoked tokens begin returning `403`
- do not expose the partner API key to browsers or client-side code

## High-level flow

1. Partner builds a consent URL to `GET /authorize`
2. User signs in to AiCred if needed
3. User approves or denies the share
4. AiCred redirects back to the registered callback URL
5. Partner exchanges `code` for `credential_token`
6. Partner stores `credential_token` against the connected user
7. Partner fetches the credential server-side when needed

## Route: `GET /authorize`

Human-facing consent flow.

### Query parameters

| Name | Required | Type | Notes |
| --- | --- | --- | --- |
| `partner` | yes | UUID | AiCred partner ID |
| `callback_url` | yes | string | must exactly match the registered callback URL and must use `https` |
| `state` | yes | string | opaque partner state value, max length `512` |

### Example

```text
https://aicred.ai/authorize?partner=PARTNER_UUID&callback_url=https%3A%2F%2Fpartner.example.com%2Fapi%2Faicred%2Fcallback&state=STATE_VALUE
```

### Approve redirect

```text
https://partner.example.com/api/aicred/callback?state=STATE_VALUE&code=AUTH_CODE
```

### Deny redirect

```text
https://partner.example.com/api/aicred/callback?state=STATE_VALUE&error=denied
```

### Behavior notes

- if the user is not signed in, AiCred redirects them to login and preserves the consent destination
- if the user profile is not public, AiCred may enable public profile visibility on approval so the shared verification URL resolves
- if the partner is invalid, inactive, or the callback URL does not match, AiCred does not issue a code

## Endpoint: `POST /functions/v1/api-token-exchange`

Exchange a short-lived auth code for a persistent `credential_token`.

### Headers

```http
Content-Type: application/json
X-AiCred-API-Key: YOUR_PARTNER_API_KEY
```

### Body

```json
{
  "code": "AUTH_CODE",
  "callback_url": "https://partner.example.com/api/aicred/callback"
}
```

### Success response

```json
{
  "credential_token": "CREDENTIAL_TOKEN",
  "token_type": "opaque",
  "user_id": "AICRED_USER_UUID"
}
```

### Status codes

| Status | Meaning |
| --- | --- |
| `200` | token exchange succeeded |
| `400` | missing `code`, missing `callback_url`, expired code, used code, or callback URL mismatch |
| `401` | invalid API key |
| `403` | origin not allowed or partner inactive |
| `429` | rate limited |

### Rate limit

- `10 requests/hour` per partner

## Endpoint: `GET /functions/v1/api-credential`

Fetch the canonical verified credential payload.

### Headers

```http
X-AiCred-API-Key: YOUR_PARTNER_API_KEY
X-Credential-Token: CREDENTIAL_TOKEN
```

### Success response

```json
{
  "credential": {
    "user": {
      "display_name": "AI Systems Operator",
      "title": "AI Infrastructure & Systems Design",
      "credential_id": "cred_abc123",
      "profile_url": "https://aicred.ai/profile/example"
    },
    "assessment": {
      "assessment_type": "initial_evaluation",
      "evaluation_type": "conversational",
      "assessed_at": "2026-03-25T12:00:00.000Z",
      "methodology": {
        "type": "conversational_evaluation",
        "sections_completed": 6,
        "scoring_method": "ai_rubric",
        "description": "Six-section conversational assessment with AI-driven follow-up and adversarial probing. Scored automatically across multiple dimensions."
      },
      "overall_result": {
        "overall_score": 9.2,
        "velocity_rating": "high",
        "component_scores": {
          "prompt_mastery": 9.1,
          "technical_understanding": 9.4,
          "practical_application": 9.0,
          "critical_evaluation": 9.3,
          "workflow_design": 9.5
        }
      },
      "sections": [
        {
          "number": 1,
          "name": "Context & Baseline",
          "score": 9.0,
          "summary": "Summary text",
          "strengths": ["Example strength"],
          "growth_areas": ["Example growth area"]
        }
      ],
      "competitive_context": "Competitive context text",
      "critical_bottleneck": "Critical bottleneck text"
    },
    "meta": {
      "api_version": "v1",
      "retrieved_at": "2026-03-25T12:10:00.000Z",
      "cache_ttl": 3600
    }
  }
}
```

### Status codes

| Status | Meaning |
| --- | --- |
| `200` | credential returned |
| `401` | invalid API key or invalid credential token |
| `403` | share revoked, share inactive, partner inactive, or browser origin not allowed |
| `404` | no completed conversational evaluation available for the connected user |
| `429` | rate limited |

### Rate limits

- `1000 requests/hour` per API key
- `60 requests/hour` per credential token

### Caching

- respect `credential.meta.cache_ttl`
- current TTL is `3600` seconds
- if you cache the payload, treat `403` as authoritative and invalidate cached access immediately

## Canonical payload contract

### `credential.user`

| Field | Type | Notes |
| --- | --- | --- |
| `display_name` | string | human-readable display name |
| `title` | string or `null` | profile headline |
| `credential_id` | string | stable opaque credential identifier |
| `profile_url` | string or `null` | public profile URL |

### `credential.assessment`

| Field | Type | Notes |
| --- | --- | --- |
| `assessment_type` | string or `null` | currently derived from stored score record |
| `evaluation_type` | string or `null` | currently derived from stored score record |
| `assessed_at` | ISO string or `null` | timestamp of the latest completed conversational evaluation used |
| `methodology` | object | assessment metadata |
| `overall_result` | object | overall score, velocity rating, and component scores |
| `sections` | array | section scores, summaries, strengths, and growth areas |
| `competitive_context` | string or `null` | public-facing context string |
| `critical_bottleneck` | string or `null` | public-facing bottleneck string |

### `credential.assessment.sections[]`

| Field | Type | Notes |
| --- | --- | --- |
| `number` | number | section number |
| `name` | string | section name |
| `score` | number or `null` | section score |
| `summary` | string | section summary |
| `strengths` | string[] | public-facing strengths |
| `growth_areas` | string[] | mapped from existing AiCred weaknesses data |

### `credential.meta`

| Field | Type | Notes |
| --- | --- | --- |
| `api_version` | string | currently `v1` |
| `retrieved_at` | ISO string | retrieval timestamp |
| `cache_ttl` | number | cache duration in seconds |

## Data that is intentionally included

The v1 payload is intentionally limited, but partners can receive and display:

- user display name, profile headline, stable credential ID, and public profile URL
- assessment type, evaluation type, assessment date, and methodology metadata
- overall score, Velocity signal, and component scores under the stable score keys
- section number, section name, section score, summary, strengths, and growth areas
- competitive context and critical bottleneck when available
- API version, retrieval timestamp, and cache TTL

## Data that is intentionally excluded

Do not expect these fields from v1:

- user email
- transcripts
- raw conversation history
- learning plans
- exercises
- quizzes
- webhook payloads
- bulk export surfaces
- score mutation APIs

## Browser and server guidance

### Server-side recommended

Use server-side fetch for `GET /functions/v1/api-credential`.

Reasons:

- the partner API key must remain secret
- server-side fetch simplifies caching and revocation handling
- browser-side fetch will require a valid `Origin` that matches the registered partner domain

### Browser-side constraints

If you call the edge endpoints from a browser:

- the browser `Origin` must match the registered partner domain
- the browser must be running on `https`
- CORS preflight is enforced

## Minimal implementation guidance for AI agents

If an AI coding agent is implementing this integration, follow this order:

1. add a server-side environment variable for `AICRED_API_KEY`
2. build the authorize URL using the registered `partner` UUID, exact `callback_url`, and your own `state`
3. store and validate `state`
4. on callback, if `error=denied`, stop cleanly
5. if `code` exists, call `POST /functions/v1/api-token-exchange`
6. persist `credential_token` against the partner-side user record
7. fetch the credential server-side with `GET /functions/v1/api-credential`
8. if the fetch returns `403`, treat the connection as revoked
9. if the fetch returns `404`, render an in-progress or unavailable state
10. do not expose the API key in client-side code

## Official component kit

- current official card version: `1.0.0`
- target stack: `Next.js + Tailwind + shadcn/ui`
- the card kit is intended to sit on top of the canonical payload, not replace it
- partner owners can retrieve the official kit from AiCred partner tools after authentication

## Reference example: callback handler

```ts
export async function GET(req: Request) {
  const url = new URL(req.url)
  const code = url.searchParams.get('code')
  const state = url.searchParams.get('state')
  const error = url.searchParams.get('error')

  // Validate state before continuing.
  if (!state || !isExpectedState(state)) {
    return new Response('Invalid state', { status: 400 })
  }

  if (error === 'denied') {
    return Response.redirect('https://partner.example.com/profile')
  }

  if (!code) {
    return new Response('Missing code', { status: 400 })
  }

  const exchange = await fetch('https://aicred.ai/functions/v1/api-token-exchange', {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      'x-aicred-api-key': process.env.AICRED_API_KEY!,
    },
    body: JSON.stringify({
      code,
      callback_url: 'https://partner.example.com/api/aicred/callback',
    }),
  })

  if (!exchange.ok) {
    return new Response('Token exchange failed', { status: exchange.status })
  }

  const data = await exchange.json()
  await saveCredentialTokenForUser(data.user_id, data.credential_token)

  return Response.redirect('https://partner.example.com/profile')
}
```

## Reference example: server-side credential fetch

```ts
export async function fetchAiCredCredential(credentialToken: string) {
  const response = await fetch('https://aicred.ai/functions/v1/api-credential', {
    headers: {
      'x-aicred-api-key': process.env.AICRED_API_KEY!,
      'x-credential-token': credentialToken,
    },
    next: { revalidate: 3600 },
  })

  if (response.status === 403) {
    return { status: 403 as const, credential: null }
  }

  if (response.status === 404) {
    return { status: 404 as const, credential: null }
  }

  if (!response.ok) {
    throw new Error(`AiCred credential fetch failed: ${response.status}`)
  }

  const json = await response.json()
  return { status: 200 as const, credential: json.credential }
}
```

## Raw source

This exact markdown file is intentionally exposed as a public artifact for machine consumption:

`https://aicred.ai/verified/aicred-verified-api-v1.md`
