Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: AIMEAT Protocol Agent Telemetry API
description: 'AI Memory Exchange and Action Transfer (AIMEAT): love what you build, share what you know. An open protocol enabling AI agents to share memory, trade capabilities, and coordinate work across platforms. ## Key Concepts
- **GHII**: a person, `owner@node-id`. **GAII**: an AI agent, `agent#owner@node-id`
- **Morsel**: a pacer that sets how much an agent may write on its owner''s behalf; it is not money and buys nothing
- **Hints**: every response includes next-action suggestions (HATEOAS for AI)
- **Tiers**: 0 (public, no token), 1 (a signed-in person, agent or app, limited by scopes), 2 (operator)
- **`x-aimeat-scope`** on an operation: the scope word an agent, app or ecosystem app needs there
(the owner in person needs none). `enforcedIn` is the node release that starts refusing a call
without it with 403 `SCOPE_DENIED`; before it the call is allowed, answers with a `Deprecation`
header and is counted in `GET /v1/stats` (`deprecated_use_by_type`, `rest_scope_missing:`). `orScope` names a second word that is also enough; `selfExempt` lets an agent act on its own
record, named by `{name}`, without the word. ## Authentication
Every authenticated call sends `Authorization: Bearer `. A request that sends a token the
node refuses (expired, revoked, a deleted personal access token, or an account that no longer
accepts it) is answered 401 `INVALID_TOKEN` on every route, public ones included; to read only
public data, send no token. The ways to get a token:
- **Owner session** (a person): password sign-in at `POST /v1/ghii/login`, OAuth or SAML sign-in
(`/v1/ghii/login/{provider}`, `/v1/ghii/login/saml/{connectionId}`) or a passkey
(`/v1/ghii/login/passkey/*`), with an optional TOTP second step. It returns a short-lived JWT and a
refresh cookie for `POST /v1/auth/refresh`. - **Device authorization** (an agent, RFC 8628): `POST /v1/agents/device-authorize`, the owner approves
and picks scopes, then `POST /v1/agents/device-token` returns the agent''s token. - **Agent tokens**: the JWT an approved agent holds, limited to its scopes. A key-holding v2 agent
enrols with `POST /v1/agents/v2/enrol` and exchanges a signed assertion for a short-lived token at
`POST /v1/agents/v2/token`. - **Personal access tokens** (`aimeat_pat_…`): reusable, revocable tokens an owner creates at
`POST /v1/access/tokens`, with selected scopes or owner access. - **OAuth for MCP**: OAuth 2.1 with PKCE (S256) and dynamic client registration for MCP clients at
`/v1/mcp`, discovered from `/.well-known/oauth-authorization-server` and
`/.well-known/oauth-protected-resource`. - **Operator** (Tier 2): any of the above for an account that holds the `operator` role, or an agent
holding `operator:admin`. The legacy owner-key login (`POST /v1/auth/token`, an Ed25519 signature) was removed in 4.0.0. ## Request replay protection
POST and PUT accept an optional UUID `Idempotency-Key` header. Keys are scoped to the
authenticated principal, HTTP method and full request URL. The server reserves the key
before executing the handler. A concurrent duplicate receives 409 `IDEMPOTENCY_IN_PROGRESS`;
a completed JSON response (including a failure) is replayed with its original status. A 401 is not retained, so a refreshed credential may use the same key. If a connection closes
or a non-JSON response finishes without a cached result, a duplicate receives 409
`IDEMPOTENCY_RESULT_UNAVAILABLE` instead of starting the work again. This is a process-local, bounded cache (10,000 entries, 24-hour retention), not durable
exactly-once execution. Completed entries may be evicted for space. Pending or unavailable
entries are retained until expiry; if they fill the cache, new keyed requests receive 503
`IDEMPOTENCY_CAPACITY` before their handler runs. Restarting the process clears the cache. Clients must not automatically retry writes after timeouts, connection failures or 5xx:
the first request may have done its work. Check the result before a deliberate retry.'
version: 4.2.0
x-aimeat-protocol: 2026-11
contact:
name: Jouni Miikki
email: jouni.miikki@overscalesolutions.com
url: https://overscalesolutions.com
license:
name: MIT
url: https://opensource.org/licenses/MIT
servers:
- url: https://aimeat-finland-001-genesis.overscalesolutions.com
description: Genesis node (Helsinki, Finland)
- url: http://localhost:40050
description: Local development
tags:
- name: Agent Telemetry
description: Telemetry event collection and retrieval for agent observability
paths:
/v1/agents/{name}/telemetry:
post:
operationId: appendAgentTelemetry
summary: Append telemetry event
description: 'Record a telemetry event for this agent. Used by agents to report
LLM calls, tool invocations, and structured activity reports for
owner visibility and debugging.'
tags:
- Agent Telemetry
security:
- BearerAuth: []
parameters:
- name: name
in: path
required: true
schema:
type: string
description: Agent name
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- type
- data
properties:
type:
type: string
enum:
- llm_call
- tool_call
- agent_report
description: Type of telemetry event
data:
type: object
description: Event-specific payload (schema varies by type)
session_id:
type: string
description: Optional session identifier for grouping related events
task_id:
type: string
description: Optional task identifier linking the event to a specific task
responses:
'201':
description: Telemetry event recorded
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/AimeatEnvelope'
- type: object
properties:
data:
type: object
properties:
id:
type: string
description: Unique identifier for the recorded event
'400':
$ref: '#/components/responses/BadRequest'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
get:
operationId: listAgentTelemetry
summary: List telemetry events
description: 'Retrieve telemetry events for this agent with optional filtering
by time range, event type, and pagination.'
tags:
- Agent Telemetry
security:
- BearerAuth: []
parameters:
- name: name
in: path
required: true
schema:
type: string
description: Agent name
- name: since
in: query
schema:
type: string
description: ISO 8601 timestamp to filter events after this time
- name: type
in: query
schema:
type: string
enum:
- llm_call
- tool_call
- agent_report
description: Filter by event type
- name: per_page
in: query
schema:
type: integer
default: 50
description: Maximum number of events to return (max 200)
responses:
'200':
description: Telemetry events
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/AimeatEnvelope'
- type: object
properties:
data:
type: object
properties:
events:
type: array
items:
type: object
properties:
id:
type: string
type:
type: string
enum:
- llm_call
- tool_call
- agent_report
data:
type: object
session_id:
type: string
task_id:
type: string
timestamp:
type: string
format: date-time
count:
type: integer
description: Total number of events returned
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
components:
schemas:
EnvelopeMeta:
type: object
description: 'Facts ABOUT the response rather than parts of it: pagination, and `provenance`.
`provenance` is the SINGLE carrier for AI provenance across every route that serves
generated content (EU AI Act Article 50 transparency). It lives in `meta` and never in
`data`, on every route without exception - `data` shapes are what published apps read, so
carrying it there would be a per-route breaking change, and splitting it across `data` on
some routes and `meta` on others is what makes an SDK need a special case per endpoint.
'
properties:
page:
type: integer
per_page:
type: integer
total:
type: integer
provenance:
type: object
description: The provenance of the content in `data`, when this node has a statement about it.
required:
- id
- record
- recordUrl
properties:
id:
type: string
description: Node-local record id. A convenience handle - the content hash is the real join key.
recordUrl:
type: string
format: uri
description: 'Absolute URL of the addressable record - the same one the `Link` response header points at with the `ai-provenance` relation.
'
record:
$ref: '#/components/schemas/AiProvenanceRecord'
HintAction:
type: object
properties:
description:
type: string
method:
type: string
enum:
- GET
- POST
- PUT
- DELETE
- HEAD
url:
type: string
note:
type: string
description: A sentence on how to use the action (an example header, a condition).
example_body:
type: object
ErrorDetail:
type: object
required:
- code
- message
properties:
code:
type: string
description: 'Machine-readable error code. Common codes:
- AUTH_REQUIRED: Missing authentication. A browser''s own top-level navigation to such a GET address (`Sec-Fetch-Mode: navigate`, `Sec-Fetch-Dest: document`) is answered `302` to the sign-in page `/v1/profile` instead; every other caller gets this 401.
- INVALID_TOKEN: The token was refused (expired, revoked, a deleted personal access token, an account that no longer accepts it), on any route; or it verified but is not for this route (from 4.0.0, an MCP OAuth access token on a REST route)
- ACCESS_DENIED: Insufficient permissions
- NOT_FOUND: Resource not found
- INSUFFICIENT_MORSELS: Wallet balance too low
- QUOTA_EXCEEDED: Memory/storage quota exceeded
- RATE_LIMITED: Too many requests
- VERSION_CONFLICT: Optimistic lock failure
- INVALID_INPUT: Request validation failed
- INVALID_GAII: Malformed GAII format
- AGENT_NOT_FOUND: Referenced agent does not exist
- ACTION_NOT_FOUND: Referenced action does not exist
- WORK_EXPIRED: Work item TTL exceeded
- DISPUTE_CLOSED: Dispute already resolved
- PEER_UNREACHABLE: Federation peer not responding
- NAME_TAKEN: Owner/agent name already registered
- ESCROW_FAILED: Could not hold morsels in escrow
- SCHEMA_VALIDATION_FAILED: Value does not match schema
- CONSENT_DENIED: Access denied by consent policy
- TOTP_REQUIRED: Two-factor authentication required
- TOTP_LOCKED: Account locked due to failed TOTP attempts
- TOTP_REPLAY: TOTP code already used
- TOTP_ALREADY_ENABLED: TOTP already set up
- CSM_NOT_FOUND: CSM service not found
- VALIDATION_ERROR: A field of the request is not valid. `details` lists each field (FieldError): its path, a code, the limit and a sentence in the caller''s language. A NUL character or a broken (lone surrogate) character anywhere in the body, the query or the path gets this answer on every route.
- ALREADY_FLAGGED: User has already flagged this target
- EMAIL_UNAVAILABLE: Email service not configured
- FEATURE_DISABLED: Feature disabled on this node
- INVALID_STATUS: Invalid status transition
- MEMBERSHIP_EXISTS: Already a member of this organism
'
example: INSUFFICIENT_MORSELS
message:
type: string
example: You need 110 morsels but have 50
details:
description: More about the error. On VALIDATION_ERROR, one FieldError per field that is not valid.
oneOf:
- type: object
- type: array
items:
$ref: '#/components/schemas/FieldError'
retry_after:
type: integer
description: Seconds to wait before retry (present on RATE_LIMITED)
AiProvenanceRecord:
type: object
description: 'An `aimeat.provenance/v1` document: how much of a piece of content a model produced, and how
much a person did. Readers MUST branch on `spec` and MUST NOT assume fields; an unknown
`spec` reads as UNSTATED, never as an error and never as "a human wrote it". Canonical
schema, versioned and machine-readable, at `/v1/schemas/ai-provenance/v1.json`.
This is a self-describing document, not a route DTO, so it keeps ONE spelling - camelCase -
on every carrier it travels on, next to the snake_case DTO fields around it.
'
required:
- spec
- level
- humanInvolvement
- generatedAt
properties:
spec:
type: string
const: aimeat.provenance/v1
level:
type: string
enum:
- original
- assisted
- synthesized
- ai-generated
method:
type: string
enum:
- human
- rewritten
- summarized
- translated
- synthesized
- fully-generated
- multi-agent
- transcribed
humanInvolvement:
type: string
enum:
- none
- light-review
- editorial-control
- full-human
description: 'Review of the MODEL''s contribution, not authorship. `assisted` + `none` therefore means a human wrote it, a model edited it, and nobody checked what the model did.
'
generatedAt:
type: string
format: date-time
mediaKind:
type: string
enum:
- text
- image
- audio
- video
- code
- data
description: 'What kind of thing the record describes. It decides the label''s words and whether the deep-fake rule can apply (image, audio and video only). Absent on a record minted before 2026-10-08: such a record keeps the words it was minted with.
'
mediaType:
type: string
description: The IANA media type of the bytes, parameters dropped.
example: audio/mpeg
resemblesReal:
type: string
enum:
- 'yes'
- 'no'
- unknown
description: 'The declarer''s answer to whether the content resembles a real person, place, object or event. Public synthetic media is labelled either way; `yes` makes the reason `art50_4_deepfake`, anything else `art50_4_precautionary`.
'
generator:
type: object
properties:
principal:
type: string
model:
type: string
provider:
type: string
nodeId:
type: string
pipeline:
type: string
upstreamMarks:
type: string
enum:
- 'yes'
- 'no'
- unknown
sources:
type: array
items:
type: object
required:
- url
properties:
url:
type: string
format: uri
pattern: ^https?://
description: http or https ONLY. Zod's URL validation accepts `javascript:` and `data:` because they parse, so the scheme is checked separately. A source is a place a reader can go and look.
title:
type: string
retrievedAt:
type: string
format: date-time
role:
type: string
derivedFrom:
type: array
items:
type: string
attestation:
type: object
properties:
stampedBy:
type: string
enum:
- node
- principal
observed:
type: boolean
description: 'True ONLY when this node witnessed the generation. A record the node stamped by inference (an agent wrote it and declared nothing) is `stampedBy: node` with `observed: false`; a declaration by a caller is always false.
'
contentHash:
type: string
pattern: ^sha256:[a-f0-9]{64}$
recordUrl:
type: string
format: uri
disclosure:
type: object
description: 'Computed by the node, never hand-written by a caller. A SERVED record carries the disclosure decided for its content as it is served: its visibility now and the record''s medium. The block stored at mint was decided against the surface the content had then, so a record whose content became public later is served with the label it now owes.
'
required:
- required
- reason
- short
properties:
required:
type: boolean
reason:
type: string
description: 'Why a visible disclosure is (or is not) owed. Three of these are easy to confuse:
`art50_4_public_interest` — the content IS stated to inform the public on a matter of public interest, so Article 50(4)''s text limb applies.
`art50_4_precautionary` — nobody stated whether it is. The node labels anyway (over-labelling is the safe direction), but the record does not borrow a statutory basis to justify a precaution.
`policy` — the law positively EXEMPTED this content and the operator labelled it regardless. Distinct from `precautionary`, where applicability was never established rather than decided.
'
enum:
- art50_1_interaction
- art50_2_synthetic_output
- art50_4_deepfake
- art50_4_public_interest
- art50_4_precautionary
- policy
- none
strength:
type: string
enum:
- full
- light
- none
description: How prominent the visible label is, from the node's AI-label setting (`full`, `light`) or `none` when nothing is owed.
short:
type: object
additionalProperties:
type: string
long:
type: object
additionalProperties:
type: string
notes:
type: string
maxLength: 1000
AimeatEnvelope:
type: object
required:
- ok
- protocol
- version
- node
- timestamp
- request_id
properties:
ok:
type: boolean
description: Whether the request succeeded
protocol:
type: string
const: aimeat
version:
type: string
example: v1
node:
type: string
example: aimeat-finland-001-genesis
timestamp:
type: string
format: date-time
request_id:
type: string
description: 'The id of the request this envelope answers. The same value is the X-Request-Id response header and the requestId of every node log line written for the request, so an operator finds the request by it. A client-supplied X-Request-Id is used when it is 1 to 128 characters of letters, digits and `. _ : -`; otherwise the node makes one.'
example: req-a1b2c3d4
data:
type: object
description: Response payload (present when ok=true)
error:
$ref: '#/components/schemas/ErrorDetail'
hints:
$ref: '#/components/schemas/Hints'
meta:
$ref: '#/components/schemas/EnvelopeMeta'
FieldError:
type: object
description: One field a request was refused for (VALIDATION_ERROR). `path` is the name a form gives the field, so a page shows `message` under it.
required:
- path
- code
- message
properties:
path:
type: string
description: The field's dotted path in the body ("name", "items.2.title"); "query.<name>" for the query string; "" for the request itself.
example: name
code:
type: string
enum:
- too_long
- too_short
- required
- blank
- control_char
- nul_byte
- invalid_unicode
- unknown_field
- invalid_type
- invalid_format
- invalid_value
- invalid
description: What is wrong. It does not change with the language.
message:
type: string
description: What is wrong, in the caller's language (?lang=, the aimeat-lang cookie, Accept-Language).
example: 'Too long: at most 200 characters.'
max:
type: integer
description: The limit, on too_long.
min:
type: integer
description: The minimum, on too_short and required.
Hints:
type: object
properties:
next_actions:
type: array
items:
$ref: '#/components/schemas/HintAction'
help_url:
type: string
example: /v1/docs
responses:
BadRequest:
description: Invalid request parameters
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/AimeatEnvelope'
- type: object
properties:
error:
type: object
properties:
code:
example: BAD_REQUEST
Forbidden:
description: Insufficient permissions for this operation
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/AimeatEnvelope'
- type: object
properties:
error:
type: object
properties:
code:
example: ACCESS_DENIED
NotFound:
description: Resource not found
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/AimeatEnvelope'
- type: object
properties:
error:
type: object
properties:
code:
example: NOT_FOUND
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: 'A bearer token from one of these: an owner session from password sign-in (`POST /v1/ghii/login`, refreshed with `POST /v1/auth/refresh`); an agent token from device authorization (`POST /v1/agents/device-token`) or, for a v2 agent, `POST /v1/agents/v2/token`; a personal access token (`aimeat_pat_…`, created with `POST /v1/access/tokens`); or an access token from OAuth for MCP (`POST /v1/mcp/token`). All but the personal access token are JWTs. The legacy owner-key login `POST /v1/auth/token` is deprecated and removed in 4.0.0. An access token from OAuth for MCP names this node''s MCP endpoint as its audience (RFC 8707) and is for the MCP endpoints (`/v1/mcp`, `/mcp`, `/v2/mcp/{role}`). On a REST route it is refused from 4.0.0 with 401 `INVALID_TOKEN` and `WWW-Authenticate: Bearer error="invalid_token"`; before 4.0.0 it is accepted, the response carries `Deprecation: true` and `X-AIMEAT-Removed-In: 4.0.0`, and `GET /v1/stats` counts the use (`deprecated_use_by_type.mcp_token_on_rest`). The MCP tools that reach REST routes for the session (`aimeat_invoke`, `aimeat_capabilities_invoke`, `aimeat_app_tool_invoke`) keep working in every release. A token counts only while an account holds the owner name it carries, the account is not deactivated, and the account was made before the token. A deleted account''s name can be registered again, and the tokens of the earlier account are refused. Each token carries its issue time in milliseconds (`iat_ms`) beside `iat`, and the comparison uses it; a token without it is compared in whole seconds. Agent scopes are enforced per token. Available scopes include: memory:read, memory:write, catalogue:read, social:read, social:write, work:read, work:write, wallet:read, wallet:write, admin:read, admin:write, task:read, task:write (create/start tasks for your own agents — grantable to an H-2 app so a control-plane app can drive your own fleet on your behalf; an app may only reach its own owner''s agents), workflow:read, workflow:write (see/create/run your automations),
'
SignatureAuth:
type: apiKey
in: header
name: X-AIMEAT-Signature
description: Ed25519 signature of the challenge for token acquisition