Skip to main content
All API endpoints (except health checks, capabilities, and pricing) require two headers:
HeaderDescription
AuthorizationYour API key, as Bearer sk_agentmark_...
X-Agentmark-App-IdYour application ID, a UUID (for example, a1b2c3d4-5678-90ab-cdef-1234567890ab). Find it on the app’s Settings page or in the Dashboard URL.
The Authorization value can be either an API key (sk_agentmark_...) or a session JWT (used by the Dashboard and agentmark login).
The REST API accepts the Authorization value with or without the Bearer prefix. The one exception is OpenTelemetry trace ingestion (POST /v1/traces via an OTLP exporter): send the raw key with no Bearer prefix, or the request fails with 401. See OpenTelemetry → Endpoint.

Creating an API key

  1. Open the AgentMark Dashboard
  2. Switch to the app you want to scope the key to (shown in the breadcrumb)
  3. Navigate to the app’s Settings → API keys page (app-level, not org-level)
  4. Click Create API key
  5. Select a role (SDK, Read-Only, or Full Access) or choose Custom to toggle individual permissions
  6. Copy the key. It’s only shown once
API keys enforce per-endpoint permissions. A key with SDK access can ingest traces and read templates but can’t delete anything. See Users and access control for the permission catalog and role presets.

API key environment scope

When you create a key, the Environment scope choice in the create-key dialog decides which environments the key can reach:
  • This environment (the default) pins the key to the environment selected in the breadcrumb. Its traces always land in that one environment, and the key can’t reach any other.
  • Environment kinds scopes the key to one or more kinds (Development, Preview, and Production) instead of pinning it. A kind-scoped key has no environment pin. Each request names the target environment (by environment name, or by the pull request number whose preview environment it targets), and the gateway authorizes that environment against the key’s allowed kinds before stamping it. The gateway rejects a selection that resolves to a kind the key can’t write to, so those traces stamp no environment rather than crossing kinds.
The kind-scoped option exists so that one key reaches every environment of the chosen kinds, including future PR preview environments. A single Preview-scoped key authorizes every PR’s preview environment as it’s created, so CI never has to provision a key per pull request. The SDK selects the target per request through initTracing({ environment, prNumber }), or through the AGENTMARK_ENVIRONMENT and AGENTMARK_PR_NUMBER variables in CI. See Attributing traces to an environment.
You create kind-scoped keys through the Dashboard’s create-key dialog. The programmatic POST /v1/api-keys route mints pinned keys only.

Endpoint permissions

Every API endpoint requires a specific permission. If your API key lacks the required permission, the request returns 403 Forbidden.

Traces and spans

EndpointPermission
POST /v1/tracestrace.write
GET /v1/tracestrace.read
POST /v1/traces/searchtrace.read
GET /v1/traces/{traceId}trace.read
GET /v1/traces/{traceId}/graphtrace.read
GET /v1/traces/{traceId}/spansspan.read
GET /v1/traces/{traceId}/spans/{spanId}span.read
GET /v1/spansspan.read
POST /v1/spans/searchspan.read
GET /v1/spans/{spanId}span.read
GET /v1/filter-schematrace.read

Sessions

EndpointPermission
GET /v1/sessionssession.read
GET /v1/sessions/{sessionId}/tracessession.read

Scores

EndpointPermission
POST /v1/scoresscore.write
POST /v1/scores/batchscore.write
GET /v1/scoresscore.read
POST /v1/scores/searchscore.read
GET /v1/scores/{scoreId}score.read
GET /v1/scores/aggregationsscore.read
GET /v1/scores/namesscore.read
DELETE /v1/scores/{scoreId}score.delete
GET /v1/score-configsscore_config.read
GET /v1/score-configs/{name}score_config.read

Metrics, datasets, experiments, prompts

EndpointPermission
GET /v1/metricsmetrics.read
GET /v1/configtemplate.read
GET /v1/datasetsdataset.read
POST /v1/datasets/{datasetName}/rowsdataset.write
POST /v1/datasets/{datasetName}/rows/from-tracesdataset.write
POST /v1/datasets/{datasetName}/rows/from-spansdataset.write
GET /v1/experimentsexperiment.read
GET /v1/experiments/{experimentId}experiment.read
GET /v1/experiments/baselineexperiment.read
GET /v1/promptstemplate.read
GET /v1/runs/{runId}/tracestrace.read
GET /v1/templates?path={filePath}template.read

Annotation queues

EndpointPermission
GET /v1/annotation-queuesannotation_queue.read
POST /v1/annotation-queuesannotation_queue.write
GET /v1/annotation-queues/{queueId}annotation_queue.read
PATCH /v1/annotation-queues/{queueId}annotation_queue.write
DELETE /v1/annotation-queues/{queueId}annotation_queue.delete
GET /v1/annotation-queues/{queueId}/itemsannotation_queue.read
POST /v1/annotation-queues/{queueId}/itemsannotation_queue.write
GET /v1/annotation-queues/{queueId}/items/{itemId}annotation_queue.read
PATCH /v1/annotation-queues/{queueId}/items/{itemId}annotation_queue.write
DELETE /v1/annotation-queues/{queueId}/items/{itemId}annotation_queue.delete
POST /v1/annotation-queues/{queueId}/items/{itemId}/reviewsannotation_queue.review

Unauthenticated

EndpointNotes
GET /v1/capabilitiesNo auth required
GET /v1/pricingNo auth required
GET /healthNo auth required
GET /v1/health/ingestionNo auth required
GET /v1/health/filesNo auth required

Making requests

curl https://api.agentmark.co/v1/traces \
  -H "Authorization: Bearer sk_agentmark_your_key_here" \
  -H "X-Agentmark-App-Id: a1b2c3d4-5678-90ab-cdef-1234567890ab"

Rate limiting

API requests are rate-limited per tenant. Limits vary by plan; see Billing and usage. When you exceed your rate limit, the API returns 429 Too Many Requests with a Retry-After header indicating how long to wait before retrying.

Span and storage limits

Trace ingestion (POST /v1/traces) enforces monthly quotas depending on your plan:
  • Span limit: maximum number of spans per month (Hobby/Growth tiers)
  • Storage cap: maximum storage used (certain plans only)
When exceeded, the API returns 429 with a span_limit_exceeded or storage_cap_exceeded error code and a Retry-After header. Upgrade your plan at Settings → Billing.

Error responses

Every /v1 error body follows the same shape, { error: { code, message } }:
{
  "error": {
    "code": "unauthorized",
    "message": "Not authorized"
  }
}
Some errors add machine-readable context as fields alongside code and message. Quota errors, for example, spread their context as flat siblings:
{
  "error": {
    "code": "span_limit_exceeded",
    "message": "Monthly span limit of 20000 reached. Upgrade your plan to raise this limit.",
    "currentCount": 20000,
    "limit": 20000,
    "upgradeUrl": "https://app.agentmark.co/settings/billing"
  }
}
400 validation errors are the one case that nests: error.message is a generic summary (Invalid request body / Invalid query parameters / Invalid path parameters), and the error.details map collects the per-field messages keyed by field name:
{
  "error": {
    "code": "invalid_request_body",
    "message": "Invalid request body",
    "details": {
      "score": "Expected number, received string"
    }
  }
}
StatusMeaning
400Invalid request. error.message is a generic summary; per-field errors are in the error.details map
401Missing or invalid Authorization / X-Agentmark-App-Id header
403API key is valid but doesn’t have the required permission for this endpoint
429Rate limited or plan quota exceeded
503Service temporarily overloaded. Retry after the interval in the Retry-After header