fix(vv): resolve all 6 V&V issues — field trial unblocked
All findings from the inaugural LeadValidator audit resolved and confirmed. Release gate: PASS. VV_ISSUE_002 (BLOCKER): 15 OpenAPI specs verified present covering all 20 route groups (46 endpoints documented in docs/openapi/) VV_ISSUE_003 (MAJOR): Remove any types from src/db/pool.ts — replaced pool.query shim with unknown[] + Object.defineProperty, zero any types, eslint-disable suppressions removed VV_ISSUE_004 (MAJOR): Remove raw Pool from ScaffoldController and HealthDetailedController — injected AgentRepository/CredentialRepository and DbProbe interface respectively; added CredentialRepository.findActiveClientId() VV_ISSUE_005 (MAJOR): Add unit tests for 5 untested services — ComplianceStatusStore, EventPublisher, MarketplaceService, OIDCTrustPolicyService, UsageService VV_ISSUE_006 (MAJOR): Add integration tests for 7 missing route groups — analytics, billing, tiers, webhooks, marketplace, oidc-trust-policies, oidc-token-exchange VV_ISSUE_001 (MINOR): Create missing design.md and tasks.md in 4 OpenSpec archives — all archives now complete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
428
docs/openapi/analytics.yaml
Normal file
428
docs/openapi/analytics.yaml
Normal file
@@ -0,0 +1,428 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — Tenant Analytics
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Tenant analytics endpoints for the SentryAgent.ai AgentIdP platform.
|
||||
|
||||
Provides usage trend data, agent activity heatmaps, and per-agent usage summaries
|
||||
scoped to the authenticated organization (tenant).
|
||||
|
||||
**All endpoints require a valid Bearer JWT.** Data is always scoped to the
|
||||
organization identified by the `organization_id` claim in the token.
|
||||
|
||||
**Feature flag:** When `ANALYTICS_ENABLED=false` these routes return 404.
|
||||
|
||||
**Available endpoints:**
|
||||
- `GET /analytics/tokens` — Daily token issuance trend (last N days)
|
||||
- `GET /analytics/agents/activity` — Agent activity heatmap by day-of-week + hour
|
||||
- `GET /analytics/agents` — Per-agent usage summary for the current month
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000/api/v1
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai/v1
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: Analytics
|
||||
description: Tenant-scoped usage analytics and reporting
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
JWT access token obtained via `POST /token`.
|
||||
Include as `Authorization: Bearer <token>`.
|
||||
|
||||
schemas:
|
||||
TokenTrendDataPoint:
|
||||
type: object
|
||||
description: Token issuance count for a single calendar day.
|
||||
required:
|
||||
- date
|
||||
- count
|
||||
properties:
|
||||
date:
|
||||
type: string
|
||||
format: date
|
||||
description: Calendar date (UTC) in `YYYY-MM-DD` format.
|
||||
example: "2026-04-01"
|
||||
count:
|
||||
type: integer
|
||||
description: Number of OAuth 2.0 tokens issued on this date.
|
||||
minimum: 0
|
||||
example: 842
|
||||
|
||||
TokenTrendResponse:
|
||||
type: object
|
||||
description: Daily token issuance trend for the last N days.
|
||||
required:
|
||||
- organizationId
|
||||
- days
|
||||
- data
|
||||
properties:
|
||||
organizationId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Organization the analytics data belongs to.
|
||||
example: "org-1234-5678-abcd-ef01"
|
||||
days:
|
||||
type: integer
|
||||
description: Number of days included in the trend window.
|
||||
example: 30
|
||||
data:
|
||||
type: array
|
||||
description: |
|
||||
Array of daily data points ordered by date ascending.
|
||||
Days with no token issuances have `count: 0`.
|
||||
items:
|
||||
$ref: '#/components/schemas/TokenTrendDataPoint'
|
||||
|
||||
ActivityHeatmapCell:
|
||||
type: object
|
||||
description: |
|
||||
A single cell in the agent activity heatmap, identified by
|
||||
day-of-week (0 = Sunday) and hour of day (0–23 UTC).
|
||||
required:
|
||||
- dayOfWeek
|
||||
- hour
|
||||
- count
|
||||
properties:
|
||||
dayOfWeek:
|
||||
type: integer
|
||||
description: Day of week (0 = Sunday, 6 = Saturday).
|
||||
minimum: 0
|
||||
maximum: 6
|
||||
example: 1
|
||||
hour:
|
||||
type: integer
|
||||
description: Hour of day in UTC (0–23).
|
||||
minimum: 0
|
||||
maximum: 23
|
||||
example: 14
|
||||
count:
|
||||
type: integer
|
||||
description: Number of token issuances or API calls in this slot.
|
||||
minimum: 0
|
||||
example: 217
|
||||
|
||||
AgentActivityResponse:
|
||||
type: object
|
||||
description: |
|
||||
Agent activity heatmap — shows when agents are most active
|
||||
by day-of-week and hour (UTC). Useful for identifying peak usage patterns.
|
||||
required:
|
||||
- organizationId
|
||||
- data
|
||||
properties:
|
||||
organizationId:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "org-1234-5678-abcd-ef01"
|
||||
data:
|
||||
type: array
|
||||
description: |
|
||||
Array of heatmap cells. Contains only cells with `count > 0`.
|
||||
Maximum 168 cells (7 days × 24 hours).
|
||||
items:
|
||||
$ref: '#/components/schemas/ActivityHeatmapCell'
|
||||
|
||||
AgentUsageSummary:
|
||||
type: object
|
||||
description: Per-agent usage summary for the current calendar month.
|
||||
required:
|
||||
- agentId
|
||||
- tokensIssued
|
||||
- apiCalls
|
||||
properties:
|
||||
agentId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: UUID of the agent.
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
agentEmail:
|
||||
type: string
|
||||
format: email
|
||||
description: Email identifier of the agent.
|
||||
example: "screener-001@sentryagent.ai"
|
||||
tokensIssued:
|
||||
type: integer
|
||||
description: Number of tokens issued for this agent in the current month.
|
||||
minimum: 0
|
||||
example: 1204
|
||||
apiCalls:
|
||||
type: integer
|
||||
description: Total API calls made by this agent in the current month.
|
||||
minimum: 0
|
||||
example: 5432
|
||||
lastActiveAt:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: Timestamp of the agent's last API activity. Null if no activity this month.
|
||||
example: "2026-04-07T08:45:00.000Z"
|
||||
|
||||
AgentSummaryResponse:
|
||||
type: object
|
||||
description: Per-agent usage summary for the current month, across all agents in the organization.
|
||||
required:
|
||||
- organizationId
|
||||
- month
|
||||
- data
|
||||
properties:
|
||||
organizationId:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "org-1234-5678-abcd-ef01"
|
||||
month:
|
||||
type: string
|
||||
description: Current billing month in `YYYY-MM` format.
|
||||
example: "2026-04"
|
||||
data:
|
||||
type: array
|
||||
description: Per-agent usage summaries, ordered by `tokensIssued` descending.
|
||||
items:
|
||||
$ref: '#/components/schemas/AgentUsageSummary'
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "UNAUTHORIZED"
|
||||
message:
|
||||
type: string
|
||||
example: "A valid Bearer token is required to access this resource."
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Missing or invalid Bearer token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "UNAUTHORIZED"
|
||||
message: "A valid Bearer token is required to access this resource."
|
||||
|
||||
InternalServerError:
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
paths:
|
||||
/analytics/tokens:
|
||||
get:
|
||||
operationId: getTokenTrend
|
||||
tags:
|
||||
- Analytics
|
||||
summary: Get daily token issuance trend
|
||||
description: |
|
||||
Returns a daily breakdown of OAuth 2.0 token issuances for the authenticated
|
||||
organization over the last N days.
|
||||
|
||||
The `days` parameter controls the window size (default: 30, max: 90).
|
||||
Days with no token activity are included with `count: 0`.
|
||||
|
||||
Data is scoped to the `organization_id` from the Bearer token.
|
||||
parameters:
|
||||
- name: days
|
||||
in: query
|
||||
required: false
|
||||
description: Number of days to include in the trend window. Default: 30, max: 90.
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 90
|
||||
default: 30
|
||||
example: 30
|
||||
responses:
|
||||
'200':
|
||||
description: Token trend data returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TokenTrendResponse'
|
||||
example:
|
||||
organizationId: "org-1234-5678-abcd-ef01"
|
||||
days: 7
|
||||
data:
|
||||
- date: "2026-04-01"
|
||||
count: 842
|
||||
- date: "2026-04-02"
|
||||
count: 967
|
||||
- date: "2026-04-03"
|
||||
count: 0
|
||||
- date: "2026-04-04"
|
||||
count: 1201
|
||||
- date: "2026-04-05"
|
||||
count: 1087
|
||||
- date: "2026-04-06"
|
||||
count: 953
|
||||
- date: "2026-04-07"
|
||||
count: 412
|
||||
'400':
|
||||
description: Invalid `days` parameter.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
examples:
|
||||
tooLarge:
|
||||
summary: Exceeds maximum
|
||||
value:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Query parameter `days` must not exceed 90."
|
||||
details:
|
||||
field: "days"
|
||||
max: 90
|
||||
provided: 120
|
||||
invalid:
|
||||
summary: Non-positive integer
|
||||
value:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Query parameter `days` must be a positive integer."
|
||||
details:
|
||||
field: "days"
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'404':
|
||||
description: Analytics feature is not enabled on this instance.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "NOT_FOUND"
|
||||
message: "Analytics is not enabled on this instance."
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/analytics/agents/activity:
|
||||
get:
|
||||
operationId: getAgentActivity
|
||||
tags:
|
||||
- Analytics
|
||||
summary: Get agent activity heatmap
|
||||
description: |
|
||||
Returns agent activity aggregated by day-of-week (0 = Sunday) and hour of day (UTC).
|
||||
|
||||
The heatmap shows when agents in the organization are most active,
|
||||
based on token issuances and API calls. Only cells with `count > 0` are returned.
|
||||
|
||||
Data is scoped to the `organization_id` from the Bearer token.
|
||||
The heatmap covers the last 90 days of activity.
|
||||
responses:
|
||||
'200':
|
||||
description: Agent activity heatmap returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AgentActivityResponse'
|
||||
example:
|
||||
organizationId: "org-1234-5678-abcd-ef01"
|
||||
data:
|
||||
- dayOfWeek: 1
|
||||
hour: 9
|
||||
count: 342
|
||||
- dayOfWeek: 1
|
||||
hour: 14
|
||||
count: 217
|
||||
- dayOfWeek: 2
|
||||
hour: 10
|
||||
count: 189
|
||||
- dayOfWeek: 3
|
||||
hour: 11
|
||||
count: 405
|
||||
- dayOfWeek: 4
|
||||
hour: 14
|
||||
count: 278
|
||||
- dayOfWeek: 5
|
||||
hour: 9
|
||||
count: 121
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'404':
|
||||
description: Analytics feature is not enabled on this instance.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "NOT_FOUND"
|
||||
message: "Analytics is not enabled on this instance."
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/analytics/agents:
|
||||
get:
|
||||
operationId: getAgentSummary
|
||||
tags:
|
||||
- Analytics
|
||||
summary: Get per-agent usage summary
|
||||
description: |
|
||||
Returns per-agent token issuance counts and API call totals for the
|
||||
current calendar month, across all agents in the authenticated organization.
|
||||
|
||||
Results are ordered by `tokensIssued` descending (most active agents first).
|
||||
|
||||
Data is scoped to the `organization_id` from the Bearer token.
|
||||
responses:
|
||||
'200':
|
||||
description: Per-agent usage summary returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AgentSummaryResponse'
|
||||
example:
|
||||
organizationId: "org-1234-5678-abcd-ef01"
|
||||
month: "2026-04"
|
||||
data:
|
||||
- agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
agentEmail: "screener-001@sentryagent.ai"
|
||||
tokensIssued: 1204
|
||||
apiCalls: 5432
|
||||
lastActiveAt: "2026-04-07T08:45:00.000Z"
|
||||
- agentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
agentEmail: "classifier-002@sentryagent.ai"
|
||||
tokensIssued: 876
|
||||
apiCalls: 3120
|
||||
lastActiveAt: "2026-04-06T14:30:00.000Z"
|
||||
- agentId: "c3d4e5f6-a7b8-9012-cdef-123456789012"
|
||||
agentEmail: "router-003@sentryagent.ai"
|
||||
tokensIssued: 0
|
||||
apiCalls: 0
|
||||
lastActiveAt: null
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'404':
|
||||
description: Analytics feature is not enabled on this instance.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "NOT_FOUND"
|
||||
message: "Analytics is not enabled on this instance."
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
355
docs/openapi/billing.yaml
Normal file
355
docs/openapi/billing.yaml
Normal file
@@ -0,0 +1,355 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — Billing & Usage Metering
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Billing and usage metering endpoints for the SentryAgent.ai AgentIdP platform.
|
||||
|
||||
Integrates with **Stripe** for subscription and payment management.
|
||||
|
||||
**Authenticated endpoints** (require Bearer JWT):
|
||||
- `POST /billing/checkout` — Create a Stripe Checkout Session for plan upgrades
|
||||
- `GET /billing/usage` — Retrieve today's usage summary
|
||||
|
||||
**Unauthenticated endpoint** (Stripe webhook receiver):
|
||||
- `POST /billing/webhook` — Receives Stripe webhook events (raw body + signature verification)
|
||||
|
||||
**Important:** The `/billing/webhook` endpoint uses `express.raw()` middleware
|
||||
to receive the raw request body as a Buffer. Do not apply `express.json()` to this route.
|
||||
The `Stripe-Signature` header is required for all webhook deliveries.
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000/api/v1
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai/v1
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: Billing Checkout
|
||||
description: Stripe Checkout Session management
|
||||
- name: Billing Webhook
|
||||
description: Stripe webhook event receiver (unauthenticated)
|
||||
- name: Usage
|
||||
description: Usage metering and reporting
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
JWT access token obtained via `POST /token`.
|
||||
Include as `Authorization: Bearer <token>`.
|
||||
|
||||
schemas:
|
||||
CheckoutRequest:
|
||||
type: object
|
||||
description: |
|
||||
Optional request body for creating a Stripe Checkout Session.
|
||||
When `successUrl` or `cancelUrl` are omitted, the platform generates
|
||||
default redirect URLs pointing to the dashboard.
|
||||
properties:
|
||||
successUrl:
|
||||
type: string
|
||||
format: uri
|
||||
description: URL to redirect to after successful payment.
|
||||
example: "https://my-app.example.com/dashboard?billing=success"
|
||||
cancelUrl:
|
||||
type: string
|
||||
format: uri
|
||||
description: URL to redirect to if the user cancels checkout.
|
||||
example: "https://my-app.example.com/dashboard?billing=cancel"
|
||||
|
||||
CheckoutResponse:
|
||||
type: object
|
||||
description: Stripe Checkout Session URL to redirect the user to.
|
||||
required:
|
||||
- checkoutUrl
|
||||
properties:
|
||||
checkoutUrl:
|
||||
type: string
|
||||
format: uri
|
||||
description: |
|
||||
Stripe-hosted Checkout page URL. Redirect the authenticated user
|
||||
to this URL to complete payment.
|
||||
example: "https://checkout.stripe.com/pay/cs_test_abcdef1234567890"
|
||||
|
||||
UsageSummary:
|
||||
type: object
|
||||
description: |
|
||||
Today's usage summary for the authenticated organization.
|
||||
Counters reset at UTC midnight.
|
||||
required:
|
||||
- organizationId
|
||||
- date
|
||||
- tokensIssued
|
||||
- agentsRegistered
|
||||
- credentialsGenerated
|
||||
properties:
|
||||
organizationId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Organization the usage data belongs to.
|
||||
example: "org-1234-5678-abcd-ef01"
|
||||
date:
|
||||
type: string
|
||||
format: date
|
||||
description: The calendar date (UTC) this summary covers.
|
||||
example: "2026-04-07"
|
||||
tokensIssued:
|
||||
type: integer
|
||||
description: Number of OAuth 2.0 tokens issued today.
|
||||
minimum: 0
|
||||
example: 4201
|
||||
agentsRegistered:
|
||||
type: integer
|
||||
description: Number of new agents registered today.
|
||||
minimum: 0
|
||||
example: 3
|
||||
credentialsGenerated:
|
||||
type: integer
|
||||
description: Number of new agent credentials generated today.
|
||||
minimum: 0
|
||||
example: 5
|
||||
apiCallsTotal:
|
||||
type: integer
|
||||
description: Total API calls across all endpoints today.
|
||||
minimum: 0
|
||||
example: 12450
|
||||
|
||||
StripeWebhookResponse:
|
||||
type: object
|
||||
description: Acknowledgement response for a received Stripe webhook event.
|
||||
required:
|
||||
- received
|
||||
properties:
|
||||
received:
|
||||
type: boolean
|
||||
description: Always `true` when the webhook was processed successfully.
|
||||
example: true
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "VALIDATION_ERROR"
|
||||
message:
|
||||
type: string
|
||||
example: "Missing Stripe-Signature header."
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Missing or invalid Bearer token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "UNAUTHORIZED"
|
||||
message: "A valid Bearer token is required to access this resource."
|
||||
|
||||
Forbidden:
|
||||
description: Valid token but insufficient permissions.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "FORBIDDEN"
|
||||
message: "You do not have permission to perform this action."
|
||||
|
||||
InternalServerError:
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
paths:
|
||||
/billing/checkout:
|
||||
post:
|
||||
operationId: createBillingCheckoutSession
|
||||
tags:
|
||||
- Billing Checkout
|
||||
summary: Create a Stripe Checkout Session
|
||||
description: |
|
||||
Creates a Stripe Checkout Session for the authenticated organization
|
||||
to upgrade their subscription plan.
|
||||
|
||||
The organization ID is read from the `organization_id` claim in the
|
||||
Bearer JWT — the caller does not need to provide it in the request body.
|
||||
|
||||
The `checkoutUrl` in the response is a Stripe-hosted checkout page.
|
||||
Redirect the authenticated user to this URL to complete payment.
|
||||
|
||||
Requires a valid Bearer JWT. The `organization_id` claim must be present in the token.
|
||||
security:
|
||||
- BearerAuth: []
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CheckoutRequest'
|
||||
example:
|
||||
successUrl: "https://my-app.example.com/dashboard?billing=success"
|
||||
cancelUrl: "https://my-app.example.com/dashboard?billing=cancel"
|
||||
responses:
|
||||
'201':
|
||||
description: Stripe Checkout Session created successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CheckoutResponse'
|
||||
example:
|
||||
checkoutUrl: "https://checkout.stripe.com/pay/cs_test_abcdef1234567890"
|
||||
'400':
|
||||
description: Validation error — organization_id missing from token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "organization_id is required in token."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'500':
|
||||
description: Unexpected error or Stripe API error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "STRIPE_ERROR"
|
||||
message: "Failed to create Stripe Checkout Session. Please try again."
|
||||
|
||||
/billing/webhook:
|
||||
post:
|
||||
operationId: handleStripeWebhook
|
||||
tags:
|
||||
- Billing Webhook
|
||||
summary: Receive Stripe webhook events
|
||||
description: |
|
||||
Receives webhook events from Stripe's delivery system. This endpoint is
|
||||
**unauthenticated** — authentication is provided by Stripe's HMAC signature
|
||||
in the `Stripe-Signature` header.
|
||||
|
||||
**Body format:** The request body MUST be the raw JSON payload as sent by
|
||||
Stripe (not parsed JSON). The `Content-Type` is `application/json` but
|
||||
the body is read as a raw `Buffer` for signature verification.
|
||||
|
||||
**Signature verification:** The `Stripe-Signature` header is required.
|
||||
If absent or invalid, the request is rejected with `400`.
|
||||
|
||||
**Supported events processed:**
|
||||
- `checkout.session.completed` — Activates subscription after payment
|
||||
- `customer.subscription.deleted` — Downgrades plan on cancellation
|
||||
- `invoice.payment_failed` — Handles failed renewals
|
||||
security: []
|
||||
parameters:
|
||||
- name: Stripe-Signature
|
||||
in: header
|
||||
required: true
|
||||
description: |
|
||||
HMAC signature from Stripe for payload verification.
|
||||
Format: `t=<timestamp>,v1=<signature>,...`
|
||||
schema:
|
||||
type: string
|
||||
example: "t=1492774577,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a05bd412fbc2a2bzo..."
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
description: Raw Stripe event payload (read as Buffer internally).
|
||||
additionalProperties: true
|
||||
example:
|
||||
id: "evt_1234567890"
|
||||
object: "event"
|
||||
type: "checkout.session.completed"
|
||||
data:
|
||||
object:
|
||||
id: "cs_test_abcdef1234567890"
|
||||
customer: "cus_abc123"
|
||||
responses:
|
||||
'200':
|
||||
description: Webhook event received and processed successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/StripeWebhookResponse'
|
||||
example:
|
||||
received: true
|
||||
'400':
|
||||
description: Missing or invalid Stripe-Signature header, or malformed payload.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
examples:
|
||||
missingSignature:
|
||||
summary: Missing Stripe-Signature header
|
||||
value:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Missing Stripe-Signature header."
|
||||
invalidSignature:
|
||||
summary: Signature verification failed
|
||||
value:
|
||||
code: "STRIPE_SIGNATURE_INVALID"
|
||||
message: "Webhook signature verification failed."
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/billing/usage:
|
||||
get:
|
||||
operationId: getBillingUsage
|
||||
tags:
|
||||
- Usage
|
||||
summary: Get today's usage summary
|
||||
description: |
|
||||
Returns the usage summary for the authenticated organization
|
||||
for the current calendar day (UTC).
|
||||
|
||||
Usage counters reset at UTC midnight.
|
||||
The `organization_id` claim is read from the Bearer JWT.
|
||||
|
||||
Requires a valid Bearer JWT.
|
||||
security:
|
||||
- BearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: Usage summary returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UsageSummary'
|
||||
example:
|
||||
organizationId: "org-1234-5678-abcd-ef01"
|
||||
date: "2026-04-07"
|
||||
tokensIssued: 4201
|
||||
agentsRegistered: 3
|
||||
credentialsGenerated: 5
|
||||
apiCallsTotal: 12450
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
480
docs/openapi/delegation.yaml
Normal file
480
docs/openapi/delegation.yaml
Normal file
@@ -0,0 +1,480 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — A2A Delegation (Agent-to-Agent)
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Agent-to-Agent (A2A) delegation endpoints for the SentryAgent.ai AgentIdP platform.
|
||||
|
||||
The delegation subsystem enables an authenticated agent (the *delegator*) to grant
|
||||
a subset of its own scopes to another agent (the *delegatee*) for a limited time.
|
||||
This creates a cryptographically-signed delegation chain, suitable for multi-agent
|
||||
orchestration patterns.
|
||||
|
||||
**All endpoints require a valid Bearer JWT.**
|
||||
|
||||
**Feature flag:** When `A2A_ENABLED=false` these routes are not registered (return 404).
|
||||
|
||||
**Delegation rules:**
|
||||
- The delegatee must be in the same tenant as the delegator.
|
||||
- Delegated scopes must be a strict subset of the delegator's own scopes.
|
||||
- TTL minimum: 60 seconds; maximum: 86400 seconds (24 hours).
|
||||
- Each delegation chain has a unique `chainId` (UUID).
|
||||
- Revoking a chain is idempotent — revoking an already-revoked chain succeeds.
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000/api/v1
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai/v1
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: A2A Delegation
|
||||
description: Agent-to-Agent delegation chain management
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
JWT access token obtained via `POST /token`.
|
||||
Include as `Authorization: Bearer <token>`.
|
||||
|
||||
schemas:
|
||||
DelegationChain:
|
||||
type: object
|
||||
description: A delegation chain record as returned by the API.
|
||||
required:
|
||||
- id
|
||||
- tenantId
|
||||
- delegatorAgentId
|
||||
- delegateeAgentId
|
||||
- scopes
|
||||
- delegationToken
|
||||
- ttlSeconds
|
||||
- issuedAt
|
||||
- expiresAt
|
||||
- createdAt
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Unique identifier of the delegation chain.
|
||||
readOnly: true
|
||||
example: "chain-abcd-1234-5678-ef01"
|
||||
tenantId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Organization (tenant) that owns this delegation.
|
||||
readOnly: true
|
||||
example: "org-1234-5678-abcd-ef01"
|
||||
delegatorAgentId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: UUID of the agent granting authority.
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
delegateeAgentId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: UUID of the agent receiving delegated authority.
|
||||
example: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
scopes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: OAuth 2.0 scopes granted by this delegation chain.
|
||||
example:
|
||||
- "agents:read"
|
||||
delegationToken:
|
||||
type: string
|
||||
description: |
|
||||
Opaque delegation token string that the delegatee presents to verify authority.
|
||||
This token encodes the chain metadata and is HMAC-signed.
|
||||
example: "chain-abcd-1234-5678-ef01.1743151200.1743237600"
|
||||
signature:
|
||||
type: string
|
||||
description: HMAC-SHA256 signature of the delegation token payload.
|
||||
example: "3a7f2b9c..."
|
||||
ttlSeconds:
|
||||
type: integer
|
||||
description: Delegation lifetime in seconds.
|
||||
minimum: 60
|
||||
maximum: 86400
|
||||
example: 3600
|
||||
issuedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-04-07T09:00:00.000Z"
|
||||
expiresAt:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-04-07T10:00:00.000Z"
|
||||
revokedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: Timestamp when this chain was revoked. Null if still active or expired naturally.
|
||||
readOnly: true
|
||||
example: null
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-04-07T09:00:00.000Z"
|
||||
|
||||
CreateDelegationRequest:
|
||||
type: object
|
||||
description: Request body for creating a new agent-to-agent delegation chain.
|
||||
required:
|
||||
- delegateeAgentId
|
||||
- scopes
|
||||
- ttlSeconds
|
||||
properties:
|
||||
delegateeAgentId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: |
|
||||
UUID of the agent to receive delegated authority.
|
||||
Must be in the same tenant as the delegator (caller).
|
||||
example: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
scopes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: |
|
||||
Scopes to delegate. Must be a strict subset of the delegator's current token scopes.
|
||||
At least one scope must be specified.
|
||||
minItems: 1
|
||||
example:
|
||||
- "agents:read"
|
||||
ttlSeconds:
|
||||
type: integer
|
||||
description: Delegation lifetime in seconds. Minimum: 60; Maximum: 86400 (24 hours).
|
||||
minimum: 60
|
||||
maximum: 86400
|
||||
example: 3600
|
||||
|
||||
VerifyDelegationRequest:
|
||||
type: object
|
||||
description: Request body for verifying a delegation token.
|
||||
required:
|
||||
- delegationToken
|
||||
properties:
|
||||
delegationToken:
|
||||
type: string
|
||||
description: The delegation token string to verify.
|
||||
example: "chain-abcd-1234-5678-ef01.1743151200.1743237600"
|
||||
|
||||
DelegationVerificationResult:
|
||||
type: object
|
||||
description: |
|
||||
Result of verifying a delegation token.
|
||||
Returns `valid: false` for expired or revoked tokens without throwing.
|
||||
required:
|
||||
- valid
|
||||
- chainId
|
||||
- delegatorAgentId
|
||||
- delegateeAgentId
|
||||
- scopes
|
||||
- issuedAt
|
||||
- expiresAt
|
||||
properties:
|
||||
valid:
|
||||
type: boolean
|
||||
description: Whether the delegation token is currently valid (active, not expired, not revoked).
|
||||
example: true
|
||||
chainId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: UUID of the delegation chain.
|
||||
example: "chain-abcd-1234-5678-ef01"
|
||||
delegatorAgentId:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
delegateeAgentId:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
scopes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
- "agents:read"
|
||||
issuedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-04-07T09:00:00.000Z"
|
||||
expiresAt:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-04-07T10:00:00.000Z"
|
||||
revokedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
example: null
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "DELEGATION_NOT_FOUND"
|
||||
message:
|
||||
type: string
|
||||
example: "Delegation chain with the specified ID was not found."
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Missing or invalid Bearer token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "UNAUTHORIZED"
|
||||
message: "A valid Bearer token is required to access this resource."
|
||||
|
||||
Forbidden:
|
||||
description: Valid token but insufficient permissions.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "FORBIDDEN"
|
||||
message: "You do not have permission to perform this action."
|
||||
|
||||
NotFound:
|
||||
description: Delegation chain not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "DELEGATION_NOT_FOUND"
|
||||
message: "Delegation chain with the specified ID was not found."
|
||||
|
||||
InternalServerError:
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
paths:
|
||||
/oauth2/token/delegate:
|
||||
post:
|
||||
operationId: createDelegation
|
||||
tags:
|
||||
- A2A Delegation
|
||||
summary: Create an A2A delegation chain
|
||||
description: |
|
||||
Creates a new agent-to-agent delegation chain. The authenticated agent
|
||||
(the *delegator*) grants a subset of its own scopes to the `delegateeAgentId`.
|
||||
|
||||
A cryptographically-signed `delegationToken` is returned. The delegatee
|
||||
presents this token to `POST /oauth2/token/verify-delegation` to prove
|
||||
delegated authority.
|
||||
|
||||
**Validation:**
|
||||
- Delegatee must be in the same organization as the delegator.
|
||||
- `scopes` must be a strict subset of the delegator's current token scopes.
|
||||
- `ttlSeconds` must be between 60 and 86400.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateDelegationRequest'
|
||||
example:
|
||||
delegateeAgentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
scopes:
|
||||
- "agents:read"
|
||||
ttlSeconds: 3600
|
||||
responses:
|
||||
'201':
|
||||
description: Delegation chain created successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DelegationChain'
|
||||
example:
|
||||
id: "chain-abcd-1234-5678-ef01"
|
||||
tenantId: "org-1234-5678-abcd-ef01"
|
||||
delegatorAgentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
delegateeAgentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
scopes:
|
||||
- "agents:read"
|
||||
delegationToken: "chain-abcd-1234-5678-ef01.1743151200.1743154800"
|
||||
signature: "3a7f2b9c..."
|
||||
ttlSeconds: 3600
|
||||
issuedAt: "2026-04-07T09:00:00.000Z"
|
||||
expiresAt: "2026-04-07T10:00:00.000Z"
|
||||
revokedAt: null
|
||||
createdAt: "2026-04-07T09:00:00.000Z"
|
||||
'400':
|
||||
description: Validation error in request body.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
examples:
|
||||
scopeExceedsOwn:
|
||||
summary: Requested scope exceeds delegator's own scopes
|
||||
value:
|
||||
code: "SCOPE_EXCEEDS_DELEGATOR"
|
||||
message: "Delegated scopes must be a subset of the delegator's own token scopes."
|
||||
details:
|
||||
requested: ["agents:write"]
|
||||
available: ["agents:read"]
|
||||
invalidTtl:
|
||||
summary: TTL out of range
|
||||
value:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "ttlSeconds must be between 60 and 86400."
|
||||
crossTenant:
|
||||
summary: Delegatee in different tenant
|
||||
value:
|
||||
code: "CROSS_TENANT_DELEGATION"
|
||||
message: "Delegatee agent must be in the same organization as the delegator."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'404':
|
||||
description: Delegatee agent not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "AGENT_NOT_FOUND"
|
||||
message: "Delegatee agent with the specified ID was not found."
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/oauth2/token/verify-delegation:
|
||||
post:
|
||||
operationId: verifyDelegation
|
||||
tags:
|
||||
- A2A Delegation
|
||||
summary: Verify a delegation token
|
||||
description: |
|
||||
Verifies a delegation token and returns the chain details if valid.
|
||||
|
||||
Returns `valid: true` with full chain metadata when the token is valid
|
||||
(exists, not expired, not revoked).
|
||||
|
||||
Returns `valid: false` when the token is expired or revoked.
|
||||
Does not throw an error for inactive tokens — always returns `200`.
|
||||
|
||||
Requires a valid Bearer JWT (any authenticated agent may verify a delegation).
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VerifyDelegationRequest'
|
||||
example:
|
||||
delegationToken: "chain-abcd-1234-5678-ef01.1743151200.1743154800"
|
||||
responses:
|
||||
'200':
|
||||
description: Delegation verification result returned.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DelegationVerificationResult'
|
||||
examples:
|
||||
valid:
|
||||
summary: Valid delegation token
|
||||
value:
|
||||
valid: true
|
||||
chainId: "chain-abcd-1234-5678-ef01"
|
||||
delegatorAgentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
delegateeAgentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
scopes:
|
||||
- "agents:read"
|
||||
issuedAt: "2026-04-07T09:00:00.000Z"
|
||||
expiresAt: "2026-04-07T10:00:00.000Z"
|
||||
revokedAt: null
|
||||
expired:
|
||||
summary: Expired delegation token
|
||||
value:
|
||||
valid: false
|
||||
chainId: "chain-abcd-1234-5678-ef01"
|
||||
delegatorAgentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
delegateeAgentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
scopes:
|
||||
- "agents:read"
|
||||
issuedAt: "2026-04-06T09:00:00.000Z"
|
||||
expiresAt: "2026-04-06T10:00:00.000Z"
|
||||
revokedAt: null
|
||||
'400':
|
||||
description: Missing or malformed `delegationToken` field.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "The 'delegationToken' field is required."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/oauth2/token/delegate/{chainId}:
|
||||
parameters:
|
||||
- name: chainId
|
||||
in: path
|
||||
required: true
|
||||
description: UUID of the delegation chain to revoke.
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "chain-abcd-1234-5678-ef01"
|
||||
|
||||
delete:
|
||||
operationId: revokeDelegation
|
||||
tags:
|
||||
- A2A Delegation
|
||||
summary: Revoke a delegation chain
|
||||
description: |
|
||||
Immediately revokes a delegation chain.
|
||||
|
||||
After revocation, `POST /oauth2/token/verify-delegation` will return
|
||||
`valid: false` for the revoked chain's token.
|
||||
|
||||
**Idempotent** — revoking an already-revoked chain returns `204` without error.
|
||||
|
||||
Only the delegator agent or an admin may revoke a chain.
|
||||
Requires a valid Bearer JWT.
|
||||
responses:
|
||||
'204':
|
||||
description: Delegation chain revoked successfully (or was already revoked). No response body.
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
576
docs/openapi/did.yaml
Normal file
576
docs/openapi/did.yaml
Normal file
@@ -0,0 +1,576 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — W3C DID & AGNTCY Agent Card
|
||||
version: 1.0.0
|
||||
description: |
|
||||
W3C Decentralized Identifier (DID) and AGNTCY Agent Card endpoints for the
|
||||
SentryAgent.ai AgentIdP platform.
|
||||
|
||||
**Unauthenticated endpoints:**
|
||||
- `GET /.well-known/did.json` — Instance-level DID Document for the IdP itself
|
||||
- `GET /api/v1/agents/:agentId/did` — Per-agent W3C DID Document
|
||||
- `GET /api/v1/agents/:agentId/did/card` — AGNTCY-format agent card
|
||||
|
||||
**Authenticated endpoint** (requires Bearer JWT + OPA authorization):
|
||||
- `GET /api/v1/agents/:agentId/did/resolve` — W3C DID Resolution result
|
||||
|
||||
All DID Documents conform to the **W3C DID Core 1.0** specification.
|
||||
Agent cards conform to the **AGNTCY** agent identity standard (Linux Foundation).
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: DID Documents
|
||||
description: W3C DID Document endpoints (unauthenticated)
|
||||
- name: DID Resolution
|
||||
description: Authenticated W3C DID Resolution endpoint
|
||||
- name: Agent Card
|
||||
description: AGNTCY agent card endpoint (unauthenticated)
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
JWT access token obtained via `POST /api/v1/token`.
|
||||
Include as `Authorization: Bearer <token>`.
|
||||
|
||||
schemas:
|
||||
PublicKeyJwk:
|
||||
type: object
|
||||
description: JWK representation of a public key embedded in a verification method.
|
||||
required:
|
||||
- kty
|
||||
properties:
|
||||
kty:
|
||||
type: string
|
||||
description: Key type (e.g. "EC", "RSA").
|
||||
example: "EC"
|
||||
crv:
|
||||
type: string
|
||||
description: EC curve (e.g. "P-256").
|
||||
example: "P-256"
|
||||
x:
|
||||
type: string
|
||||
description: Base64url-encoded EC x coordinate.
|
||||
example: "f83OJ3D..."
|
||||
y:
|
||||
type: string
|
||||
description: Base64url-encoded EC y coordinate.
|
||||
example: "x_FEzRu..."
|
||||
n:
|
||||
type: string
|
||||
description: Base64url-encoded RSA modulus.
|
||||
e:
|
||||
type: string
|
||||
description: Base64url-encoded RSA public exponent.
|
||||
example: "AQAB"
|
||||
use:
|
||||
type: string
|
||||
example: "sig"
|
||||
kid:
|
||||
type: string
|
||||
example: "key-20260328-001"
|
||||
|
||||
VerificationMethod:
|
||||
type: object
|
||||
description: W3C DID Core 1.0 verification method.
|
||||
required:
|
||||
- id
|
||||
- type
|
||||
- controller
|
||||
- publicKeyJwk
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: Full DID URL for this verification method.
|
||||
example: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890#key-1"
|
||||
type:
|
||||
type: string
|
||||
description: Verification method type.
|
||||
example: "JsonWebKey2020"
|
||||
controller:
|
||||
type: string
|
||||
description: DID that controls this key.
|
||||
example: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
publicKeyJwk:
|
||||
$ref: '#/components/schemas/PublicKeyJwk'
|
||||
|
||||
DIDService:
|
||||
type: object
|
||||
description: A W3C DID Document service endpoint.
|
||||
required:
|
||||
- id
|
||||
- type
|
||||
- serviceEndpoint
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
example: "did:web:api.sentryagent.ai:agents:a1b2c3d4#agentIdP"
|
||||
type:
|
||||
type: string
|
||||
example: "AgentIdP"
|
||||
serviceEndpoint:
|
||||
type: string
|
||||
format: uri
|
||||
example: "https://api.sentryagent.ai/api/v1/agents/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
|
||||
AgntcyExtension:
|
||||
type: object
|
||||
description: AGNTCY-specific extension fields embedded in a per-agent DID Document.
|
||||
required:
|
||||
- agentId
|
||||
- agentType
|
||||
- capabilities
|
||||
- deploymentEnv
|
||||
- owner
|
||||
- version
|
||||
properties:
|
||||
agentId:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
agentType:
|
||||
type: string
|
||||
example: "screener"
|
||||
capabilities:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ["resume:read", "email:send"]
|
||||
deploymentEnv:
|
||||
type: string
|
||||
example: "production"
|
||||
owner:
|
||||
type: string
|
||||
example: "talent-acquisition-team"
|
||||
version:
|
||||
type: string
|
||||
example: "1.4.2"
|
||||
|
||||
DIDDocument:
|
||||
type: object
|
||||
description: W3C DID Core 1.0 DID Document.
|
||||
required:
|
||||
- "@context"
|
||||
- id
|
||||
- controller
|
||||
- verificationMethod
|
||||
- authentication
|
||||
properties:
|
||||
"@context":
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: JSON-LD context URIs.
|
||||
example:
|
||||
- "https://www.w3.org/ns/did/v1"
|
||||
- "https://w3id.org/security/suites/jws-2020/v1"
|
||||
id:
|
||||
type: string
|
||||
description: The DID identifier for this document.
|
||||
example: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
controller:
|
||||
type: string
|
||||
description: DID of the controlling entity.
|
||||
example: "did:web:api.sentryagent.ai"
|
||||
verificationMethod:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VerificationMethod'
|
||||
authentication:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: DID URLs referencing verification methods authorized for authentication.
|
||||
example:
|
||||
- "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890#key-1"
|
||||
assertionMethod:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
service:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/DIDService'
|
||||
agntcy:
|
||||
$ref: '#/components/schemas/AgntcyExtension'
|
||||
|
||||
DIDResolutionResult:
|
||||
type: object
|
||||
description: |
|
||||
W3C DID Resolution result format.
|
||||
Returned with `Content-Type: application/ld+json;profile="https://w3id.org/did-resolution"`.
|
||||
required:
|
||||
- didDocument
|
||||
- didDocumentMetadata
|
||||
- didResolutionMetadata
|
||||
properties:
|
||||
didDocument:
|
||||
$ref: '#/components/schemas/DIDDocument'
|
||||
didDocumentMetadata:
|
||||
type: object
|
||||
required:
|
||||
- created
|
||||
- updated
|
||||
- deactivated
|
||||
properties:
|
||||
created:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-03-01T08:00:00.000Z"
|
||||
updated:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-03-28T11:30:00.000Z"
|
||||
deactivated:
|
||||
type: boolean
|
||||
example: false
|
||||
didResolutionMetadata:
|
||||
type: object
|
||||
required:
|
||||
- contentType
|
||||
- retrieved
|
||||
properties:
|
||||
contentType:
|
||||
type: string
|
||||
example: "application/ld+json"
|
||||
retrieved:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-04-07T09:00:00.000Z"
|
||||
|
||||
AgentCard:
|
||||
type: object
|
||||
description: |
|
||||
AGNTCY-format agent card providing a machine-readable identity summary.
|
||||
Suitable for AGNTCY registry publishing and agent discovery.
|
||||
required:
|
||||
- did
|
||||
- name
|
||||
- agentType
|
||||
- capabilities
|
||||
- owner
|
||||
- version
|
||||
- deploymentEnv
|
||||
- identityProvider
|
||||
- issuedAt
|
||||
properties:
|
||||
did:
|
||||
type: string
|
||||
description: W3C DID identifier for this agent.
|
||||
example: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
name:
|
||||
type: string
|
||||
description: Human-readable agent name (derived from email).
|
||||
example: "screener-001@sentryagent.ai"
|
||||
agentType:
|
||||
type: string
|
||||
example: "screener"
|
||||
capabilities:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ["resume:read", "email:send", "candidate:score"]
|
||||
owner:
|
||||
type: string
|
||||
example: "talent-acquisition-team"
|
||||
version:
|
||||
type: string
|
||||
example: "1.4.2"
|
||||
deploymentEnv:
|
||||
type: string
|
||||
example: "production"
|
||||
identityProvider:
|
||||
type: string
|
||||
format: uri
|
||||
description: URL of the issuing AgentIdP instance.
|
||||
example: "https://api.sentryagent.ai"
|
||||
issuedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: ISO 8601 timestamp when this card was generated.
|
||||
example: "2026-04-07T09:00:00.000Z"
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "AGENT_NOT_FOUND"
|
||||
message:
|
||||
type: string
|
||||
example: "Agent with the specified ID was not found."
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Missing or invalid Bearer token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "UNAUTHORIZED"
|
||||
message: "A valid Bearer token is required to access this resource."
|
||||
|
||||
Forbidden:
|
||||
description: Valid token but insufficient permissions.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "FORBIDDEN"
|
||||
message: "You do not have permission to perform this action."
|
||||
|
||||
NotFound:
|
||||
description: Agent not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "AGENT_NOT_FOUND"
|
||||
message: "Agent with the specified ID was not found."
|
||||
|
||||
InternalServerError:
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
paths:
|
||||
/.well-known/did.json:
|
||||
get:
|
||||
operationId: getInstanceDIDDocument
|
||||
tags:
|
||||
- DID Documents
|
||||
summary: Instance-level DID Document
|
||||
description: |
|
||||
Returns the W3C DID Document for the SentryAgent.ai AgentIdP instance itself.
|
||||
This identifies the IdP as a DID controller (`did:web:api.sentryagent.ai`).
|
||||
|
||||
Used by external parties to discover the IdP's public keys and service endpoints.
|
||||
This endpoint is **unauthenticated**.
|
||||
security: []
|
||||
responses:
|
||||
'200':
|
||||
description: Instance DID Document returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DIDDocument'
|
||||
example:
|
||||
"@context":
|
||||
- "https://www.w3.org/ns/did/v1"
|
||||
- "https://w3id.org/security/suites/jws-2020/v1"
|
||||
id: "did:web:api.sentryagent.ai"
|
||||
controller: "did:web:api.sentryagent.ai"
|
||||
verificationMethod:
|
||||
- id: "did:web:api.sentryagent.ai#key-1"
|
||||
type: "JsonWebKey2020"
|
||||
controller: "did:web:api.sentryagent.ai"
|
||||
publicKeyJwk:
|
||||
kty: "EC"
|
||||
crv: "P-256"
|
||||
x: "f83OJ3D..."
|
||||
y: "x_FEzRu..."
|
||||
authentication:
|
||||
- "did:web:api.sentryagent.ai#key-1"
|
||||
service:
|
||||
- id: "did:web:api.sentryagent.ai#agentIdP"
|
||||
type: "AgentIdP"
|
||||
serviceEndpoint: "https://api.sentryagent.ai/api/v1"
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/api/v1/agents/{agentId}/did:
|
||||
get:
|
||||
operationId: getAgentDIDDocument
|
||||
tags:
|
||||
- DID Documents
|
||||
summary: Get agent DID Document
|
||||
description: |
|
||||
Returns the W3C DID Core 1.0 Document for a specific registered agent.
|
||||
|
||||
Returns `410 Gone` if the agent has been decommissioned — the DID Document
|
||||
is no longer active.
|
||||
|
||||
This endpoint is **unauthenticated**.
|
||||
security: []
|
||||
parameters:
|
||||
- name: agentId
|
||||
in: path
|
||||
required: true
|
||||
description: UUID of the agent.
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
responses:
|
||||
'200':
|
||||
description: Agent DID Document returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DIDDocument'
|
||||
example:
|
||||
"@context":
|
||||
- "https://www.w3.org/ns/did/v1"
|
||||
- "https://w3id.org/security/suites/jws-2020/v1"
|
||||
id: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
controller: "did:web:api.sentryagent.ai"
|
||||
verificationMethod:
|
||||
- id: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890#key-1"
|
||||
type: "JsonWebKey2020"
|
||||
controller: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
publicKeyJwk:
|
||||
kty: "EC"
|
||||
crv: "P-256"
|
||||
x: "f83OJ3D..."
|
||||
y: "x_FEzRu..."
|
||||
authentication:
|
||||
- "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890#key-1"
|
||||
agntcy:
|
||||
agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
agentType: "screener"
|
||||
capabilities:
|
||||
- "resume:read"
|
||||
- "email:send"
|
||||
deploymentEnv: "production"
|
||||
owner: "talent-acquisition-team"
|
||||
version: "1.4.2"
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'410':
|
||||
description: Agent has been decommissioned — DID Document is no longer active.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "AGENT_DECOMMISSIONED"
|
||||
message: "Agent has been decommissioned — DID Document is no longer active"
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/api/v1/agents/{agentId}/did/resolve:
|
||||
get:
|
||||
operationId: resolveAgentDID
|
||||
tags:
|
||||
- DID Resolution
|
||||
summary: Resolve agent DID (W3C DID Resolution)
|
||||
description: |
|
||||
Returns the full W3C DID Resolution result for a specific agent, including
|
||||
the DID Document, DID Document Metadata, and DID Resolution Metadata.
|
||||
|
||||
The response `Content-Type` is:
|
||||
`application/ld+json;profile="https://w3id.org/did-resolution"`
|
||||
|
||||
Requires a valid Bearer JWT and OPA authorization.
|
||||
security:
|
||||
- BearerAuth: []
|
||||
parameters:
|
||||
- name: agentId
|
||||
in: path
|
||||
required: true
|
||||
description: UUID of the agent to resolve.
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
responses:
|
||||
'200':
|
||||
description: DID Resolution result returned successfully.
|
||||
content:
|
||||
application/ld+json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DIDResolutionResult'
|
||||
example:
|
||||
didDocument:
|
||||
"@context":
|
||||
- "https://www.w3.org/ns/did/v1"
|
||||
id: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
controller: "did:web:api.sentryagent.ai"
|
||||
verificationMethod: []
|
||||
authentication: []
|
||||
didDocumentMetadata:
|
||||
created: "2026-03-01T08:00:00.000Z"
|
||||
updated: "2026-03-28T11:30:00.000Z"
|
||||
deactivated: false
|
||||
didResolutionMetadata:
|
||||
contentType: "application/ld+json"
|
||||
retrieved: "2026-04-07T09:00:00.000Z"
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/api/v1/agents/{agentId}/did/card:
|
||||
get:
|
||||
operationId: getAgentCard
|
||||
tags:
|
||||
- Agent Card
|
||||
summary: Get AGNTCY agent card
|
||||
description: |
|
||||
Returns the AGNTCY-format agent card for the specified agent.
|
||||
The card provides a machine-readable identity summary suitable for
|
||||
AGNTCY registry publishing and agent discovery by external consumers.
|
||||
|
||||
This endpoint is **unauthenticated**.
|
||||
security: []
|
||||
parameters:
|
||||
- name: agentId
|
||||
in: path
|
||||
required: true
|
||||
description: UUID of the agent.
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
responses:
|
||||
'200':
|
||||
description: AGNTCY agent card returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AgentCard'
|
||||
example:
|
||||
did: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
name: "screener-001@sentryagent.ai"
|
||||
agentType: "screener"
|
||||
capabilities:
|
||||
- "resume:read"
|
||||
- "email:send"
|
||||
- "candidate:score"
|
||||
owner: "talent-acquisition-team"
|
||||
version: "1.4.2"
|
||||
deploymentEnv: "production"
|
||||
identityProvider: "https://api.sentryagent.ai"
|
||||
issuedAt: "2026-04-07T09:00:00.000Z"
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
639
docs/openapi/federation.yaml
Normal file
639
docs/openapi/federation.yaml
Normal file
@@ -0,0 +1,639 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — Identity Federation
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Cross-IdP identity federation endpoints for the SentryAgent.ai AgentIdP platform.
|
||||
|
||||
The federation subsystem enables this IdP to trust and verify JWT tokens issued by
|
||||
external identity providers (partner IdPs). This allows agents from federated
|
||||
organizations to authenticate without re-registering.
|
||||
|
||||
**Partner management** (`admin:orgs` scope required):
|
||||
- `POST /federation/trust` — Register a new trusted partner IdP
|
||||
- `GET /federation/partners` — List all registered partners
|
||||
- `GET /federation/partners/:id` — Get a specific partner
|
||||
- `PATCH /federation/partners/:id` — Update a partner
|
||||
- `DELETE /federation/partners/:id` — Remove a partner
|
||||
|
||||
**Token verification** (any authenticated agent):
|
||||
- `POST /federation/verify` — Verify a federated JWT from a trusted partner
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000/api/v1
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai/v1
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: Federation Partners
|
||||
description: Trusted partner IdP management (admin:orgs scope)
|
||||
- name: Federation Verification
|
||||
description: Cross-IdP token verification (any authenticated agent)
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
JWT access token obtained via `POST /token`.
|
||||
Include as `Authorization: Bearer <token>`.
|
||||
|
||||
schemas:
|
||||
FederationPartnerStatus:
|
||||
type: string
|
||||
enum:
|
||||
- active
|
||||
- suspended
|
||||
- expired
|
||||
description: |
|
||||
Lifecycle status of a federation partner.
|
||||
- `active` — partner is trusted; tokens accepted.
|
||||
- `suspended` — temporarily disabled; tokens rejected.
|
||||
- `expired` — `expires_at` has passed; tokens rejected.
|
||||
example: active
|
||||
|
||||
FederationPartner:
|
||||
type: object
|
||||
description: A registered trusted federation partner IdP.
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- issuer
|
||||
- jwks_uri
|
||||
- allowed_organizations
|
||||
- status
|
||||
- created_at
|
||||
- updated_at
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Immutable system-assigned UUID for this partner.
|
||||
readOnly: true
|
||||
example: "fed-abcd-1234-5678-ef01"
|
||||
name:
|
||||
type: string
|
||||
description: Human-readable partner name.
|
||||
example: "Acme Partner IdP"
|
||||
issuer:
|
||||
type: string
|
||||
format: uri
|
||||
description: Issuer URL — must match the `iss` claim in federated tokens.
|
||||
example: "https://idp.acme-partner.example"
|
||||
jwks_uri:
|
||||
type: string
|
||||
format: uri
|
||||
description: URL of the partner's JWKS endpoint for token signature verification.
|
||||
example: "https://idp.acme-partner.example/.well-known/jwks.json"
|
||||
allowed_organizations:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: |
|
||||
Allowlist of organization_id values accepted from this partner.
|
||||
An empty array means all organizations from this partner are accepted.
|
||||
example: ["org-acme-001", "org-acme-002"]
|
||||
status:
|
||||
$ref: '#/components/schemas/FederationPartnerStatus'
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-03-01T08:00:00.000Z"
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-03-28T11:30:00.000Z"
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: Optional expiry timestamp. Null means the partner never expires.
|
||||
example: "2027-01-01T00:00:00.000Z"
|
||||
|
||||
CreatePartnerRequest:
|
||||
type: object
|
||||
description: Request body for registering a new trusted federation partner.
|
||||
required:
|
||||
- name
|
||||
- issuer
|
||||
- jwks_uri
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: Human-readable partner name.
|
||||
minLength: 1
|
||||
maxLength: 256
|
||||
example: "Acme Partner IdP"
|
||||
issuer:
|
||||
type: string
|
||||
format: uri
|
||||
description: Issuer URL of the external IdP. Must match `iss` in federated tokens.
|
||||
example: "https://idp.acme-partner.example"
|
||||
jwks_uri:
|
||||
type: string
|
||||
format: uri
|
||||
description: |
|
||||
URL of the partner's JWKS endpoint. Must be reachable at registration time —
|
||||
the endpoint is probed before the partner is persisted.
|
||||
example: "https://idp.acme-partner.example/.well-known/jwks.json"
|
||||
allowed_organizations:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Optional allowlist of organization_id values. Defaults to empty (all allowed).
|
||||
example: ["org-acme-001"]
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Optional ISO 8601 date-time after which the partner will be automatically expired.
|
||||
example: "2027-01-01T00:00:00.000Z"
|
||||
|
||||
UpdatePartnerRequest:
|
||||
type: object
|
||||
description: |
|
||||
Request body for partially updating a federation partner.
|
||||
All fields are optional; only provided fields are updated.
|
||||
minProperties: 1
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 256
|
||||
example: "Acme Partner IdP (Updated)"
|
||||
jwks_uri:
|
||||
type: string
|
||||
format: uri
|
||||
description: Updated JWKS endpoint URL. The JWKS cache will be invalidated on update.
|
||||
example: "https://idp.acme-partner.example/.well-known/jwks.json"
|
||||
allowed_organizations:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ["org-acme-001", "org-acme-003"]
|
||||
status:
|
||||
$ref: '#/components/schemas/FederationPartnerStatus'
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: Updated expiry. Pass null to remove the expiry.
|
||||
example: "2028-01-01T00:00:00.000Z"
|
||||
|
||||
FederationVerifyRequest:
|
||||
type: object
|
||||
description: Request body for verifying a federated token from a trusted partner.
|
||||
required:
|
||||
- token
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
description: The JWT token string issued by the partner IdP.
|
||||
example: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZ2VudC0wMDEiLCJpc3MiOiJodHRwczovL2lkcC5hY21lLXBhcnRuZXIuZXhhbXBsZSIsImV4cCI6MTc0MzE1NDgwMH0.signature"
|
||||
expected_issuer:
|
||||
type: string
|
||||
format: uri
|
||||
description: |
|
||||
Optional issuer hint. When provided, the token's `iss` claim must match exactly.
|
||||
Useful to prevent issuer confusion when multiple partners share JWKS.
|
||||
example: "https://idp.acme-partner.example"
|
||||
|
||||
FederationVerifyResult:
|
||||
type: object
|
||||
description: Result of a successful federated token verification.
|
||||
required:
|
||||
- valid
|
||||
- issuer
|
||||
- subject
|
||||
- claims
|
||||
properties:
|
||||
valid:
|
||||
type: boolean
|
||||
description: Whether the token is valid (true when verification passed).
|
||||
example: true
|
||||
issuer:
|
||||
type: string
|
||||
format: uri
|
||||
description: The `iss` claim from the verified token.
|
||||
example: "https://idp.acme-partner.example"
|
||||
subject:
|
||||
type: string
|
||||
description: The `sub` claim from the verified token.
|
||||
example: "agent-001-external"
|
||||
organization_id:
|
||||
type: string
|
||||
description: The `organization_id` claim from the token, if present.
|
||||
example: "org-acme-001"
|
||||
claims:
|
||||
type: object
|
||||
description: All claims from the verified token payload.
|
||||
additionalProperties: true
|
||||
example:
|
||||
iss: "https://idp.acme-partner.example"
|
||||
sub: "agent-001-external"
|
||||
aud: "https://api.sentryagent.ai"
|
||||
iat: 1743151200
|
||||
exp: 1743154800
|
||||
organization_id: "org-acme-001"
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "FEDERATION_PARTNER_NOT_FOUND"
|
||||
message:
|
||||
type: string
|
||||
example: "Federation partner with the specified ID was not found."
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Missing or invalid Bearer token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "UNAUTHORIZED"
|
||||
message: "A valid Bearer token is required to access this resource."
|
||||
|
||||
Forbidden:
|
||||
description: Valid token but insufficient permissions.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "FORBIDDEN"
|
||||
message: "You do not have permission to perform this action."
|
||||
|
||||
NotFound:
|
||||
description: Federation partner not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "FEDERATION_PARTNER_NOT_FOUND"
|
||||
message: "Federation partner with the specified ID was not found."
|
||||
|
||||
InternalServerError:
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
paths:
|
||||
/federation/trust:
|
||||
post:
|
||||
operationId: registerFederationPartner
|
||||
tags:
|
||||
- Federation Partners
|
||||
summary: Register a trusted federation partner
|
||||
description: |
|
||||
Registers a new external IdP as a trusted federation partner.
|
||||
|
||||
The partner's JWKS endpoint (`jwks_uri`) is probed at registration time
|
||||
to confirm it is reachable. If the endpoint is unreachable, the request
|
||||
is rejected with `422 Unprocessable Entity`.
|
||||
|
||||
Requires `admin:orgs` scope.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreatePartnerRequest'
|
||||
example:
|
||||
name: "Acme Partner IdP"
|
||||
issuer: "https://idp.acme-partner.example"
|
||||
jwks_uri: "https://idp.acme-partner.example/.well-known/jwks.json"
|
||||
allowed_organizations:
|
||||
- "org-acme-001"
|
||||
expires_at: "2027-01-01T00:00:00.000Z"
|
||||
responses:
|
||||
'201':
|
||||
description: Federation partner registered successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FederationPartner'
|
||||
example:
|
||||
id: "fed-abcd-1234-5678-ef01"
|
||||
name: "Acme Partner IdP"
|
||||
issuer: "https://idp.acme-partner.example"
|
||||
jwks_uri: "https://idp.acme-partner.example/.well-known/jwks.json"
|
||||
allowed_organizations:
|
||||
- "org-acme-001"
|
||||
status: "active"
|
||||
created_at: "2026-04-07T09:00:00.000Z"
|
||||
updated_at: "2026-04-07T09:00:00.000Z"
|
||||
expires_at: "2027-01-01T00:00:00.000Z"
|
||||
'400':
|
||||
description: Validation error in request body.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Request validation failed."
|
||||
details:
|
||||
field: "jwks_uri"
|
||||
reason: "Must be a valid HTTPS URL."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'409':
|
||||
description: A partner with the same issuer is already registered.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "FEDERATION_PARTNER_CONFLICT"
|
||||
message: "A federation partner with this issuer is already registered."
|
||||
'422':
|
||||
description: Partner JWKS endpoint is unreachable.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "JWKS_UNREACHABLE"
|
||||
message: "The partner JWKS endpoint is unreachable. Verify the URL and try again."
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/federation/partners:
|
||||
get:
|
||||
operationId: listFederationPartners
|
||||
tags:
|
||||
- Federation Partners
|
||||
summary: List all federation partners
|
||||
description: |
|
||||
Returns all registered federation partners.
|
||||
Partners past their `expires_at` are returned with status `expired`.
|
||||
|
||||
Requires `admin:orgs` scope.
|
||||
responses:
|
||||
'200':
|
||||
description: List of federation partners returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/FederationPartner'
|
||||
example:
|
||||
- id: "fed-abcd-1234-5678-ef01"
|
||||
name: "Acme Partner IdP"
|
||||
issuer: "https://idp.acme-partner.example"
|
||||
jwks_uri: "https://idp.acme-partner.example/.well-known/jwks.json"
|
||||
allowed_organizations: ["org-acme-001"]
|
||||
status: "active"
|
||||
created_at: "2026-03-01T08:00:00.000Z"
|
||||
updated_at: "2026-03-28T11:30:00.000Z"
|
||||
expires_at: "2027-01-01T00:00:00.000Z"
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/federation/partners/{id}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: UUID of the federation partner.
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "fed-abcd-1234-5678-ef01"
|
||||
|
||||
get:
|
||||
operationId: getFederationPartner
|
||||
tags:
|
||||
- Federation Partners
|
||||
summary: Get a federation partner by ID
|
||||
description: |
|
||||
Returns a single federation partner record by its UUID.
|
||||
Requires `admin:orgs` scope.
|
||||
responses:
|
||||
'200':
|
||||
description: Federation partner returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FederationPartner'
|
||||
example:
|
||||
id: "fed-abcd-1234-5678-ef01"
|
||||
name: "Acme Partner IdP"
|
||||
issuer: "https://idp.acme-partner.example"
|
||||
jwks_uri: "https://idp.acme-partner.example/.well-known/jwks.json"
|
||||
allowed_organizations: ["org-acme-001"]
|
||||
status: "active"
|
||||
created_at: "2026-03-01T08:00:00.000Z"
|
||||
updated_at: "2026-03-28T11:30:00.000Z"
|
||||
expires_at: null
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
patch:
|
||||
operationId: updateFederationPartner
|
||||
tags:
|
||||
- Federation Partners
|
||||
summary: Update a federation partner
|
||||
description: |
|
||||
Partially updates a federation partner's configuration.
|
||||
Only provided fields are updated.
|
||||
|
||||
Changing `jwks_uri` invalidates the cached JWKS for this partner.
|
||||
Requires `admin:orgs` scope.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UpdatePartnerRequest'
|
||||
example:
|
||||
status: "suspended"
|
||||
allowed_organizations:
|
||||
- "org-acme-001"
|
||||
- "org-acme-003"
|
||||
responses:
|
||||
'200':
|
||||
description: Federation partner updated successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FederationPartner'
|
||||
example:
|
||||
id: "fed-abcd-1234-5678-ef01"
|
||||
name: "Acme Partner IdP"
|
||||
issuer: "https://idp.acme-partner.example"
|
||||
jwks_uri: "https://idp.acme-partner.example/.well-known/jwks.json"
|
||||
allowed_organizations: ["org-acme-001", "org-acme-003"]
|
||||
status: "suspended"
|
||||
created_at: "2026-03-01T08:00:00.000Z"
|
||||
updated_at: "2026-04-07T09:00:00.000Z"
|
||||
expires_at: null
|
||||
'400':
|
||||
description: Validation error in request body.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Request validation failed."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
delete:
|
||||
operationId: deleteFederationPartner
|
||||
tags:
|
||||
- Federation Partners
|
||||
summary: Remove a federation partner
|
||||
description: |
|
||||
Permanently removes a federation partner. After removal, tokens issued
|
||||
by the partner's IdP will no longer be accepted.
|
||||
|
||||
Requires `admin:orgs` scope.
|
||||
responses:
|
||||
'204':
|
||||
description: Federation partner removed successfully. No response body.
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/federation/verify:
|
||||
post:
|
||||
operationId: verifyFederatedToken
|
||||
tags:
|
||||
- Federation Verification
|
||||
summary: Verify a federated JWT token
|
||||
description: |
|
||||
Verifies a JWT token issued by a registered federation partner.
|
||||
|
||||
The token's `iss` claim is matched against registered active partners.
|
||||
The signature is verified against the partner's JWKS.
|
||||
The partner's `allowed_organizations` filter is applied if non-empty.
|
||||
|
||||
Returns verification result with claims on success.
|
||||
Returns `422` with verification failure details on invalid tokens.
|
||||
|
||||
Requires any valid Bearer token (`agents:read` scope is sufficient).
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FederationVerifyRequest'
|
||||
example:
|
||||
token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZ2VudC0wMDEiLCJpc3MiOiJodHRwczovL2lkcC5hY21lLXBhcnRuZXIuZXhhbXBsZSJ9.signature"
|
||||
expected_issuer: "https://idp.acme-partner.example"
|
||||
responses:
|
||||
'200':
|
||||
description: Token verification result returned.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FederationVerifyResult'
|
||||
examples:
|
||||
valid:
|
||||
summary: Token verified successfully
|
||||
value:
|
||||
valid: true
|
||||
issuer: "https://idp.acme-partner.example"
|
||||
subject: "agent-001-external"
|
||||
organization_id: "org-acme-001"
|
||||
claims:
|
||||
iss: "https://idp.acme-partner.example"
|
||||
sub: "agent-001-external"
|
||||
aud: "https://api.sentryagent.ai"
|
||||
iat: 1743151200
|
||||
exp: 1743154800
|
||||
invalid:
|
||||
summary: Token invalid or expired
|
||||
value:
|
||||
valid: false
|
||||
issuer: ""
|
||||
subject: ""
|
||||
claims: {}
|
||||
'400':
|
||||
description: Missing or malformed token in request body.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "The 'token' field is required."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'422':
|
||||
description: |
|
||||
Token is well-formed but verification failed (unknown issuer, expired,
|
||||
invalid signature, or organization not in allowlist).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
examples:
|
||||
unknownIssuer:
|
||||
summary: Unknown issuer
|
||||
value:
|
||||
code: "UNKNOWN_FEDERATION_ISSUER"
|
||||
message: "No active federation partner found for this token issuer."
|
||||
partnerSuspended:
|
||||
summary: Partner is suspended
|
||||
value:
|
||||
code: "FEDERATION_PARTNER_SUSPENDED"
|
||||
message: "The federation partner for this token issuer is suspended."
|
||||
orgNotAllowed:
|
||||
summary: Organization not in allowlist
|
||||
value:
|
||||
code: "FEDERATION_ORG_NOT_ALLOWED"
|
||||
message: "The token's organization_id is not in the partner's allowed_organizations list."
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
291
docs/openapi/health.yaml
Normal file
291
docs/openapi/health.yaml
Normal file
@@ -0,0 +1,291 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — Health Check Service
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Liveness and readiness health endpoints for the SentryAgent.ai AgentIdP platform.
|
||||
|
||||
Both endpoints are **unauthenticated** — safe to call from monitoring systems,
|
||||
load balancers, and container orchestrators without credentials.
|
||||
|
||||
**GET /health** performs a fast liveness check (< 50 ms target).
|
||||
**GET /health/detailed** probes each dependency with latency measurement.
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: Health
|
||||
description: Liveness and dependency health endpoints
|
||||
|
||||
components:
|
||||
schemas:
|
||||
ServiceSimpleStatus:
|
||||
type: string
|
||||
enum:
|
||||
- connected
|
||||
- disconnected
|
||||
description: Simple connectivity status for the quick health check.
|
||||
example: connected
|
||||
|
||||
ServiceDetailedStatus:
|
||||
type: string
|
||||
enum:
|
||||
- healthy
|
||||
- degraded
|
||||
- unreachable
|
||||
description: |
|
||||
Per-service health classification for the detailed health check.
|
||||
- `healthy` — responded within 1000 ms
|
||||
- `degraded` — responded but latency exceeded 1000 ms
|
||||
- `unreachable` — timed out or threw an error
|
||||
|
||||
ServiceHealthResult:
|
||||
type: object
|
||||
description: Per-service latency and status result from the detailed health probe.
|
||||
required:
|
||||
- status
|
||||
- latencyMs
|
||||
properties:
|
||||
status:
|
||||
$ref: '#/components/schemas/ServiceDetailedStatus'
|
||||
latencyMs:
|
||||
type: integer
|
||||
description: Probe round-trip time in milliseconds.
|
||||
example: 12
|
||||
|
||||
HealthResponse:
|
||||
type: object
|
||||
description: Response body for GET /health — quick liveness check.
|
||||
required:
|
||||
- status
|
||||
- version
|
||||
- uptime
|
||||
- services
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- ok
|
||||
- degraded
|
||||
description: |
|
||||
Overall liveness status.
|
||||
- `ok` — all services are connected.
|
||||
- `degraded` — one or more services are disconnected.
|
||||
example: ok
|
||||
version:
|
||||
type: string
|
||||
description: Running npm package version.
|
||||
example: "1.0.0"
|
||||
uptime:
|
||||
type: integer
|
||||
description: Process uptime in whole seconds.
|
||||
example: 3724
|
||||
services:
|
||||
type: object
|
||||
description: Quick connectivity check for core services.
|
||||
required:
|
||||
- postgres
|
||||
- redis
|
||||
properties:
|
||||
postgres:
|
||||
$ref: '#/components/schemas/ServiceSimpleStatus'
|
||||
redis:
|
||||
$ref: '#/components/schemas/ServiceSimpleStatus'
|
||||
|
||||
DetailedHealthResponse:
|
||||
type: object
|
||||
description: |
|
||||
Response body for GET /health/detailed. Probes each dependency
|
||||
individually and reports per-service latency.
|
||||
required:
|
||||
- status
|
||||
- version
|
||||
- uptime
|
||||
- services
|
||||
properties:
|
||||
status:
|
||||
$ref: '#/components/schemas/ServiceDetailedStatus'
|
||||
description: Worst-case overall status across all probed services.
|
||||
example: healthy
|
||||
version:
|
||||
type: string
|
||||
description: Running npm package version.
|
||||
example: "1.0.0"
|
||||
uptime:
|
||||
type: integer
|
||||
description: Process uptime in whole seconds.
|
||||
example: 3724
|
||||
services:
|
||||
type: object
|
||||
description: |
|
||||
Map of service name to per-service health result.
|
||||
Always includes `postgres`; `redis`, `vault`, and `opa` are
|
||||
included when the respective client / env-var is configured.
|
||||
additionalProperties:
|
||||
$ref: '#/components/schemas/ServiceHealthResult'
|
||||
example:
|
||||
postgres:
|
||||
status: healthy
|
||||
latencyMs: 12
|
||||
redis:
|
||||
status: healthy
|
||||
latencyMs: 3
|
||||
vault:
|
||||
status: degraded
|
||||
latencyMs: 1240
|
||||
opa:
|
||||
status: healthy
|
||||
latencyMs: 8
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "INTERNAL_SERVER_ERROR"
|
||||
message:
|
||||
type: string
|
||||
example: "An unexpected error occurred. Please try again later."
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
paths:
|
||||
/health:
|
||||
get:
|
||||
operationId: getHealth
|
||||
tags:
|
||||
- Health
|
||||
summary: Quick liveness check
|
||||
description: |
|
||||
Returns `200 OK` when PostgreSQL and Redis are reachable.
|
||||
Returns `503 Service Unavailable` when either dependency is disconnected.
|
||||
|
||||
This endpoint is **unauthenticated** — no Bearer token is required.
|
||||
Designed for load-balancer health checks and uptime monitors.
|
||||
security: []
|
||||
responses:
|
||||
'200':
|
||||
description: All services are connected and the application is healthy.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HealthResponse'
|
||||
example:
|
||||
status: ok
|
||||
version: "1.0.0"
|
||||
uptime: 3724
|
||||
services:
|
||||
postgres: connected
|
||||
redis: connected
|
||||
'503':
|
||||
description: One or more services are disconnected. The application is degraded.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HealthResponse'
|
||||
example:
|
||||
status: degraded
|
||||
version: "1.0.0"
|
||||
uptime: 3724
|
||||
services:
|
||||
postgres: connected
|
||||
redis: disconnected
|
||||
'500':
|
||||
description: Unexpected server error during health probe.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: INTERNAL_SERVER_ERROR
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
/health/detailed:
|
||||
get:
|
||||
operationId: getHealthDetailed
|
||||
tags:
|
||||
- Health
|
||||
summary: Detailed dependency health with latency
|
||||
description: |
|
||||
Probes each configured dependency (PostgreSQL, Redis, Vault, OPA) with a
|
||||
3000 ms timeout and reports per-service status and latency.
|
||||
|
||||
**HTTP status codes:**
|
||||
- `200` — all probed services are `healthy`
|
||||
- `207` — at least one service is `degraded` but none are `unreachable`
|
||||
- `503` — at least one service is `unreachable`
|
||||
|
||||
This endpoint is **unauthenticated**.
|
||||
Vault and OPA entries are omitted when not configured via environment variables.
|
||||
security: []
|
||||
responses:
|
||||
'200':
|
||||
description: All probed services are healthy (latency < 1000 ms).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DetailedHealthResponse'
|
||||
example:
|
||||
status: healthy
|
||||
version: "1.0.0"
|
||||
uptime: 3724
|
||||
services:
|
||||
postgres:
|
||||
status: healthy
|
||||
latencyMs: 12
|
||||
redis:
|
||||
status: healthy
|
||||
latencyMs: 3
|
||||
'207':
|
||||
description: At least one service is degraded (latency > 1000 ms) but none are unreachable.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DetailedHealthResponse'
|
||||
example:
|
||||
status: degraded
|
||||
version: "1.0.0"
|
||||
uptime: 3724
|
||||
services:
|
||||
postgres:
|
||||
status: healthy
|
||||
latencyMs: 14
|
||||
redis:
|
||||
status: degraded
|
||||
latencyMs: 1350
|
||||
'503':
|
||||
description: At least one service is unreachable (timed out or connection refused).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DetailedHealthResponse'
|
||||
example:
|
||||
status: unreachable
|
||||
version: "1.0.0"
|
||||
uptime: 3724
|
||||
services:
|
||||
postgres:
|
||||
status: unreachable
|
||||
latencyMs: 3000
|
||||
redis:
|
||||
status: healthy
|
||||
latencyMs: 4
|
||||
'500':
|
||||
description: Unexpected server error during health probe.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: INTERNAL_SERVER_ERROR
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
388
docs/openapi/marketplace.yaml
Normal file
388
docs/openapi/marketplace.yaml
Normal file
@@ -0,0 +1,388 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — Public Agent Marketplace
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Public Agent Marketplace endpoints for the SentryAgent.ai AgentIdP platform.
|
||||
|
||||
The marketplace enables discovery of AI agents that have been explicitly
|
||||
marked as public (`isPublic: true`) by their owners. It is a read-only
|
||||
public catalog — no authentication required.
|
||||
|
||||
**Feature flag:** When `MARKETPLACE_ENABLED=false` all routes return `404`.
|
||||
|
||||
**Unauthenticated endpoints:**
|
||||
- `GET /marketplace/agents` — Paginated list of public agents with search and filters
|
||||
- `GET /marketplace/agents/:agentId` — Detailed public agent with DID Document
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000/api/v1
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai/v1
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: Marketplace
|
||||
description: Public agent discovery endpoints (unauthenticated)
|
||||
|
||||
components:
|
||||
schemas:
|
||||
MinimalDIDDocument:
|
||||
type: object
|
||||
description: Minimal W3C DID Document returned with marketplace agent detail.
|
||||
required:
|
||||
- "@context"
|
||||
- id
|
||||
- controller
|
||||
- verificationMethod
|
||||
- authentication
|
||||
properties:
|
||||
"@context":
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
- "https://www.w3.org/ns/did/v1"
|
||||
id:
|
||||
type: string
|
||||
example: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
controller:
|
||||
type: string
|
||||
example: "did:web:api.sentryagent.ai"
|
||||
verificationMethod:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
description: Verification methods from the DID Document.
|
||||
authentication:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
MarketplaceAgentCard:
|
||||
type: object
|
||||
description: |
|
||||
Public agent card returned by the marketplace. Contains only information
|
||||
that the agent owner has explicitly made public.
|
||||
required:
|
||||
- agentId
|
||||
- agentType
|
||||
- version
|
||||
- capabilities
|
||||
- owner
|
||||
- deploymentEnv
|
||||
- publishedAt
|
||||
properties:
|
||||
agentId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Unique identifier of the agent.
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
agentType:
|
||||
type: string
|
||||
description: Functional classification of the agent.
|
||||
enum:
|
||||
- screener
|
||||
- classifier
|
||||
- orchestrator
|
||||
- extractor
|
||||
- summarizer
|
||||
- router
|
||||
- monitor
|
||||
- custom
|
||||
example: "screener"
|
||||
version:
|
||||
type: string
|
||||
description: Semantic version string of the agent.
|
||||
example: "1.4.2"
|
||||
capabilities:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: List of capability strings (`resource:action` format).
|
||||
example:
|
||||
- "resume:read"
|
||||
- "email:send"
|
||||
- "candidate:score"
|
||||
owner:
|
||||
type: string
|
||||
description: Team or organization that owns this agent.
|
||||
example: "talent-acquisition-team"
|
||||
deploymentEnv:
|
||||
type: string
|
||||
enum:
|
||||
- development
|
||||
- staging
|
||||
- production
|
||||
example: "production"
|
||||
did:
|
||||
type: string
|
||||
nullable: true
|
||||
description: W3C DID identifier for this agent, if one has been generated.
|
||||
example: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
didDocument:
|
||||
$ref: '#/components/schemas/MinimalDIDDocument'
|
||||
nullable: true
|
||||
description: Minimal W3C DID Document for this agent, if a DID has been generated.
|
||||
publishedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Timestamp when this agent was made public.
|
||||
example: "2026-03-01T08:00:00.000Z"
|
||||
|
||||
PaginatedMarketplaceResponse:
|
||||
type: object
|
||||
description: Paginated marketplace listing response.
|
||||
required:
|
||||
- data
|
||||
- total
|
||||
- page
|
||||
- limit
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/MarketplaceAgentCard'
|
||||
total:
|
||||
type: integer
|
||||
description: Total number of public agents matching the query.
|
||||
example: 58
|
||||
page:
|
||||
type: integer
|
||||
description: Current page number (1-based).
|
||||
example: 1
|
||||
limit:
|
||||
type: integer
|
||||
description: Number of results per page.
|
||||
example: 20
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "AGENT_NOT_FOUND"
|
||||
message:
|
||||
type: string
|
||||
example: "Agent with the specified ID was not found."
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
paths:
|
||||
/marketplace/agents:
|
||||
get:
|
||||
operationId: listPublicAgents
|
||||
tags:
|
||||
- Marketplace
|
||||
summary: List public agents
|
||||
description: |
|
||||
Returns a paginated list of agents that have been marked as public.
|
||||
|
||||
Supports full-text search across `owner`, `capabilities`, and `agentType`
|
||||
via the `q` parameter. Results can be filtered by `capability` or `publisher`.
|
||||
|
||||
**Unauthenticated** — no Bearer token required.
|
||||
|
||||
Returns `404` when the `MARKETPLACE_ENABLED` feature flag is set to `false`.
|
||||
security: []
|
||||
parameters:
|
||||
- name: q
|
||||
in: query
|
||||
required: false
|
||||
description: |
|
||||
Full-text search query across agent owner, capabilities, and agent type.
|
||||
schema:
|
||||
type: string
|
||||
example: "resume screener"
|
||||
- name: capability
|
||||
in: query
|
||||
required: false
|
||||
description: Filter by a specific capability string (exact match).
|
||||
schema:
|
||||
type: string
|
||||
example: "resume:read"
|
||||
- name: publisher
|
||||
in: query
|
||||
required: false
|
||||
description: Filter by owner/publisher name (exact match).
|
||||
schema:
|
||||
type: string
|
||||
example: "talent-acquisition-team"
|
||||
- name: page
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
default: 1
|
||||
example: 1
|
||||
- name: limit
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
default: 20
|
||||
example: 20
|
||||
responses:
|
||||
'200':
|
||||
description: Paginated list of public agents returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PaginatedMarketplaceResponse'
|
||||
example:
|
||||
data:
|
||||
- agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
agentType: "screener"
|
||||
version: "1.4.2"
|
||||
capabilities:
|
||||
- "resume:read"
|
||||
- "email:send"
|
||||
- "candidate:score"
|
||||
owner: "talent-acquisition-team"
|
||||
deploymentEnv: "production"
|
||||
did: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
didDocument: null
|
||||
publishedAt: "2026-03-01T08:00:00.000Z"
|
||||
- agentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
agentType: "classifier"
|
||||
version: "2.1.0"
|
||||
capabilities:
|
||||
- "document:classify"
|
||||
- "label:write"
|
||||
owner: "platform-team"
|
||||
deploymentEnv: "staging"
|
||||
did: null
|
||||
didDocument: null
|
||||
publishedAt: "2026-03-10T10:00:00.000Z"
|
||||
total: 58
|
||||
page: 1
|
||||
limit: 20
|
||||
'400':
|
||||
description: Invalid query parameters.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Invalid query parameter value."
|
||||
details:
|
||||
field: "limit"
|
||||
reason: "Must be between 1 and 100."
|
||||
'404':
|
||||
description: Marketplace is not enabled on this instance.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "NOT_FOUND"
|
||||
message: "The Agent Marketplace is not enabled on this instance."
|
||||
'500':
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
/marketplace/agents/{agentId}:
|
||||
get:
|
||||
operationId: getPublicAgent
|
||||
tags:
|
||||
- Marketplace
|
||||
summary: Get public agent detail
|
||||
description: |
|
||||
Returns the full public profile for a specific marketplace-listed agent,
|
||||
including its DID Document (if a DID has been generated).
|
||||
|
||||
Only agents with `isPublic: true` are accessible via this endpoint.
|
||||
Attempting to retrieve a private agent returns `404`.
|
||||
|
||||
**Unauthenticated** — no Bearer token required.
|
||||
|
||||
Returns `404` when the `MARKETPLACE_ENABLED` feature flag is set to `false`.
|
||||
security: []
|
||||
parameters:
|
||||
- name: agentId
|
||||
in: path
|
||||
required: true
|
||||
description: UUID of the public agent.
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
responses:
|
||||
'200':
|
||||
description: Public agent detail returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/MarketplaceAgentCard'
|
||||
example:
|
||||
agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
agentType: "screener"
|
||||
version: "1.4.2"
|
||||
capabilities:
|
||||
- "resume:read"
|
||||
- "email:send"
|
||||
- "candidate:score"
|
||||
owner: "talent-acquisition-team"
|
||||
deploymentEnv: "production"
|
||||
did: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
didDocument:
|
||||
"@context":
|
||||
- "https://www.w3.org/ns/did/v1"
|
||||
id: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
controller: "did:web:api.sentryagent.ai"
|
||||
verificationMethod:
|
||||
- id: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890#key-1"
|
||||
type: "JsonWebKey2020"
|
||||
controller: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
publicKeyJwk:
|
||||
kty: "EC"
|
||||
crv: "P-256"
|
||||
x: "f83OJ3D..."
|
||||
y: "x_FEzRu..."
|
||||
authentication:
|
||||
- id: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890#key-1"
|
||||
publishedAt: "2026-03-01T08:00:00.000Z"
|
||||
'404':
|
||||
description: Agent not found, not public, or marketplace not enabled.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
examples:
|
||||
agentNotFound:
|
||||
summary: Agent not found or not public
|
||||
value:
|
||||
code: "AGENT_NOT_FOUND"
|
||||
message: "Agent with the specified ID was not found."
|
||||
marketplaceDisabled:
|
||||
summary: Marketplace feature disabled
|
||||
value:
|
||||
code: "NOT_FOUND"
|
||||
message: "The Agent Marketplace is not enabled on this instance."
|
||||
'500':
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
106
docs/openapi/metrics.yaml
Normal file
106
docs/openapi/metrics.yaml
Normal file
@@ -0,0 +1,106 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — Prometheus Metrics Endpoint
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Internal Prometheus metrics endpoint for the SentryAgent.ai AgentIdP platform.
|
||||
|
||||
This endpoint returns metrics in **Prometheus text exposition format** (v0.0.4).
|
||||
It is intended exclusively for internal Prometheus scraping.
|
||||
|
||||
**Security notice:** This endpoint is **unauthenticated** and MUST NOT be
|
||||
exposed on a public-facing network interface. Restrict access via network
|
||||
policy, firewall rules, or a reverse-proxy that only allows Prometheus
|
||||
scraper IP ranges to reach `/metrics`.
|
||||
|
||||
Metrics exported include:
|
||||
- HTTP request counts and latencies (by route and status code)
|
||||
- Token issuance, introspection, and revocation counters
|
||||
- Agent registration and decommission counters
|
||||
- Active registered agent gauge
|
||||
- Database connection pool metrics
|
||||
- Process memory and CPU metrics (via `prom-client` defaults)
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000
|
||||
description: Local development server (internal only)
|
||||
- url: https://api.sentryagent.ai
|
||||
description: Production server (restrict to Prometheus scraper)
|
||||
|
||||
tags:
|
||||
- name: Metrics
|
||||
description: Prometheus metrics scrape endpoint
|
||||
|
||||
components:
|
||||
schemas:
|
||||
PrometheusMetrics:
|
||||
type: string
|
||||
description: |
|
||||
Metrics in Prometheus text exposition format (v0.0.4).
|
||||
Each metric family is preceded by `# HELP` and `# TYPE` comment lines.
|
||||
example: |
|
||||
# HELP process_cpu_user_seconds_total Total user CPU time spent in seconds.
|
||||
# TYPE process_cpu_user_seconds_total counter
|
||||
process_cpu_user_seconds_total 0.123456
|
||||
# HELP http_requests_total Total number of HTTP requests.
|
||||
# TYPE http_requests_total counter
|
||||
http_requests_total{method="POST",route="/api/v1/token",status_code="200"} 4201
|
||||
http_requests_total{method="GET",route="/api/v1/agents",status_code="200"} 987
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "INTERNAL_SERVER_ERROR"
|
||||
message:
|
||||
type: string
|
||||
example: "An unexpected error occurred. Please try again later."
|
||||
|
||||
paths:
|
||||
/metrics:
|
||||
get:
|
||||
operationId: scrapeMetrics
|
||||
tags:
|
||||
- Metrics
|
||||
summary: Prometheus metrics scrape endpoint
|
||||
description: |
|
||||
Returns all registered metrics in Prometheus text exposition format.
|
||||
|
||||
The `Content-Type` header in the response is set to the value reported
|
||||
by the `prom-client` registry (`text/plain; version=0.0.4; charset=utf-8`).
|
||||
|
||||
This endpoint is **unauthenticated** and is intended for internal
|
||||
Prometheus scraping only. Do not expose on public interfaces.
|
||||
security: []
|
||||
responses:
|
||||
'200':
|
||||
description: Metrics in Prometheus text exposition format.
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PrometheusMetrics'
|
||||
example: |
|
||||
# HELP process_cpu_user_seconds_total Total user CPU time spent in seconds.
|
||||
# TYPE process_cpu_user_seconds_total counter
|
||||
process_cpu_user_seconds_total 0.123456
|
||||
# HELP sentryagent_tokens_issued_total Total tokens issued.
|
||||
# TYPE sentryagent_tokens_issued_total counter
|
||||
sentryagent_tokens_issued_total 4201
|
||||
# HELP sentryagent_agents_registered_total Total agents registered.
|
||||
# TYPE sentryagent_agents_registered_total counter
|
||||
sentryagent_agents_registered_total 120
|
||||
'500':
|
||||
description: Unexpected error while collecting metrics.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: INTERNAL_SERVER_ERROR
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
228
docs/openapi/oidc-token-exchange.yaml
Normal file
228
docs/openapi/oidc-token-exchange.yaml
Normal file
@@ -0,0 +1,228 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — OIDC Token Exchange (Workload Identity Federation)
|
||||
version: 1.0.0
|
||||
description: |
|
||||
OIDC Token Exchange endpoint for Workload Identity Federation on the
|
||||
SentryAgent.ai AgentIdP platform.
|
||||
|
||||
This endpoint allows CI/CD workflows (e.g. GitHub Actions) to exchange their
|
||||
short-lived OIDC JWT for a SentryAgent.ai Bearer access token without requiring
|
||||
long-lived credentials stored as secrets.
|
||||
|
||||
**Flow:**
|
||||
1. Tenant creates a trust policy via `POST /api/v1/oidc/trust-policies`
|
||||
linking a GitHub repo + optional branch to an agent UUID.
|
||||
2. In the GitHub Actions workflow, fetch the OIDC token:
|
||||
```
|
||||
id-token: write # required permissions block
|
||||
OIDC_TOKEN=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
|
||||
"$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r .value)
|
||||
```
|
||||
3. POST the GitHub OIDC token to `POST /api/v1/oidc/token`.
|
||||
4. Receive a SentryAgent.ai Bearer token valid for 1 hour.
|
||||
|
||||
**This endpoint is unauthenticated** — the GitHub OIDC JWT IS the credential.
|
||||
Trust-policy enforcement is performed server-side.
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000/api/v1
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai/v1
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: OIDC Token Exchange
|
||||
description: Workload Identity Federation token exchange (unauthenticated)
|
||||
|
||||
components:
|
||||
schemas:
|
||||
OIDCTokenExchangeRequest:
|
||||
type: object
|
||||
description: |
|
||||
Request body for exchanging a GitHub OIDC JWT for a SentryAgent.ai access token.
|
||||
required:
|
||||
- token
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
description: |
|
||||
The GitHub OIDC JWT obtained from the GitHub Actions token request endpoint.
|
||||
Must be a valid, unexpired JWT signed by GitHub.
|
||||
example: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyZXBvOmFjbWUtY29ycC9hZ2VudC1kZXBsb3llcjpyZWY6cmVmcy9oZWFkcy9tYWluIiwiaXNzIjoiaHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImV4cCI6MTc0MzE1NDgwMH0.signature"
|
||||
|
||||
OIDCTokenExchangeResponse:
|
||||
type: object
|
||||
description: SentryAgent.ai Bearer access token issued in exchange for the OIDC JWT.
|
||||
required:
|
||||
- access_token
|
||||
- token_type
|
||||
- expires_in
|
||||
- scope
|
||||
properties:
|
||||
access_token:
|
||||
type: string
|
||||
description: |
|
||||
Signed JWT access token. Use as `Authorization: Bearer <access_token>`
|
||||
in subsequent API calls.
|
||||
example: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhMWIyYzNkNC1lNWY2LTc4OTAtYWJjZC1lZjEyMzQ1Njc4OTAiLCJzY29wZSI6ImFnZW50czpyZWFkIGFnZW50czp3cml0ZSIsImlhdCI6MTc0MzE1MTIwMCwiZXhwIjoxNzQzMTU0ODAwfQ.signature"
|
||||
token_type:
|
||||
type: string
|
||||
enum:
|
||||
- Bearer
|
||||
description: Token type. Always `Bearer`.
|
||||
example: "Bearer"
|
||||
expires_in:
|
||||
type: integer
|
||||
description: Token lifetime in seconds from issuance. Default is `3600`.
|
||||
example: 3600
|
||||
scope:
|
||||
type: string
|
||||
description: Space-separated OAuth 2.0 scopes granted by this token.
|
||||
example: "agents:read agents:write"
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "TRUST_POLICY_NOT_FOUND"
|
||||
message:
|
||||
type: string
|
||||
example: "No trust policy matches the provided OIDC token claims."
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
responses:
|
||||
InternalServerError:
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
paths:
|
||||
/oidc/token:
|
||||
post:
|
||||
operationId: exchangeOIDCToken
|
||||
tags:
|
||||
- OIDC Token Exchange
|
||||
summary: Exchange a GitHub OIDC JWT for a SentryAgent.ai access token
|
||||
description: |
|
||||
Exchanges a GitHub Actions OIDC JWT for a SentryAgent.ai Bearer access token.
|
||||
|
||||
**Verification steps performed server-side:**
|
||||
1. Decode and validate the GitHub OIDC JWT signature against GitHub's JWKS.
|
||||
2. Verify the token is not expired.
|
||||
3. Match the token's `sub` claim (format: `repo:<org>/<repo>:ref:refs/heads/<branch>`)
|
||||
against registered trust policies.
|
||||
4. Apply branch constraint if the matching policy has one.
|
||||
5. Verify the linked agent is active and not decommissioned.
|
||||
6. Issue a SentryAgent.ai access token scoped to the linked agent.
|
||||
|
||||
**This endpoint is unauthenticated** — the GitHub OIDC JWT serves as the credential.
|
||||
|
||||
**GitHub Actions example:**
|
||||
```yaml
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Get OIDC token and exchange
|
||||
run: |
|
||||
GITHUB_TOKEN=$(curl -sH "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
|
||||
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api.sentryagent.ai" | jq -r .value)
|
||||
AGENT_TOKEN=$(curl -sX POST https://api.sentryagent.ai/api/v1/oidc/token \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"token\": \"$GITHUB_TOKEN\"}" | jq -r .access_token)
|
||||
```
|
||||
security: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OIDCTokenExchangeRequest'
|
||||
example:
|
||||
token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyZXBvOmFjbWUtY29ycC9hZ2VudC1kZXBsb3llcjpyZWY6cmVmcy9oZWFkcy9tYWluIiwiaXNzIjoiaHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbSJ9.signature"
|
||||
responses:
|
||||
'200':
|
||||
description: SentryAgent.ai access token issued successfully.
|
||||
headers:
|
||||
Cache-Control:
|
||||
schema:
|
||||
type: string
|
||||
description: Token responses must not be cached.
|
||||
example: "no-store"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OIDCTokenExchangeResponse'
|
||||
example:
|
||||
access_token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhMWIyYzNkNC1lNWY2LTc4OTAtYWJjZC1lZjEyMzQ1Njc4OTAiLCJzY29wZSI6ImFnZW50czpyZWFkIGFnZW50czp3cml0ZSIsImlhdCI6MTc0MzE1MTIwMCwiZXhwIjoxNzQzMTU0ODAwfQ.signature"
|
||||
token_type: "Bearer"
|
||||
expires_in: 3600
|
||||
scope: "agents:read agents:write"
|
||||
'400':
|
||||
description: Missing or malformed `token` field in request body.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "The 'token' field is required."
|
||||
'401':
|
||||
description: The provided OIDC token is invalid, expired, or has an invalid signature.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
examples:
|
||||
expired:
|
||||
summary: Token is expired
|
||||
value:
|
||||
code: "OIDC_TOKEN_EXPIRED"
|
||||
message: "The provided GitHub OIDC token has expired."
|
||||
invalidSignature:
|
||||
summary: Invalid signature
|
||||
value:
|
||||
code: "OIDC_TOKEN_INVALID"
|
||||
message: "The provided GitHub OIDC token signature is invalid."
|
||||
'403':
|
||||
description: Token claims do not match any active trust policy, or the linked agent is inactive.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
examples:
|
||||
noPolicy:
|
||||
summary: No matching trust policy
|
||||
value:
|
||||
code: "TRUST_POLICY_NOT_FOUND"
|
||||
message: "No trust policy matches the provided OIDC token claims."
|
||||
branchMismatch:
|
||||
summary: Branch constraint not satisfied
|
||||
value:
|
||||
code: "TRUST_POLICY_BRANCH_MISMATCH"
|
||||
message: "The token's branch does not match the trust policy constraint."
|
||||
details:
|
||||
allowed: "main"
|
||||
provided: "feature/xyz"
|
||||
agentSuspended:
|
||||
summary: Linked agent is suspended
|
||||
value:
|
||||
code: "AGENT_SUSPENDED"
|
||||
message: "The agent linked to this trust policy is currently suspended."
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
384
docs/openapi/oidc-trust-policies.yaml
Normal file
384
docs/openapi/oidc-trust-policies.yaml
Normal file
@@ -0,0 +1,384 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — OIDC Trust Policies
|
||||
version: 1.0.0
|
||||
description: |
|
||||
OIDC trust policy management endpoints for the SentryAgent.ai AgentIdP platform.
|
||||
|
||||
Trust policies allow tenants to configure Workload Identity Federation:
|
||||
workflows running in a trusted OIDC provider (e.g. GitHub Actions) can exchange
|
||||
their OIDC JWT for a SentryAgent.ai access token without long-lived credentials.
|
||||
|
||||
**Supported OIDC providers:** `github`
|
||||
|
||||
**Workflow:**
|
||||
1. Create a trust policy linking a GitHub repo (+ optional branch) to an agent UUID
|
||||
2. In your GitHub Actions workflow, call `POST /api/v1/oidc/token` with the GitHub OIDC JWT
|
||||
3. Receive a SentryAgent.ai Bearer token scoped to the linked agent
|
||||
|
||||
**All endpoints require a valid Bearer JWT.**
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000/api/v1
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai/v1
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: OIDC Trust Policies
|
||||
description: Workload Identity Federation trust policy management
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
JWT access token obtained via `POST /token`.
|
||||
Include as `Authorization: Bearer <token>`.
|
||||
|
||||
schemas:
|
||||
OIDCProvider:
|
||||
type: string
|
||||
enum:
|
||||
- github
|
||||
description: |
|
||||
Supported OIDC provider identifier.
|
||||
Currently only "github" is supported; the list is extensible.
|
||||
example: github
|
||||
|
||||
OIDCTrustPolicy:
|
||||
type: object
|
||||
description: A persisted OIDC trust policy record.
|
||||
required:
|
||||
- id
|
||||
- provider
|
||||
- repository
|
||||
- agentId
|
||||
- createdAt
|
||||
- updatedAt
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Immutable system-assigned UUID for this trust policy.
|
||||
readOnly: true
|
||||
example: "tp-abcd-1234-5678-ef01"
|
||||
provider:
|
||||
$ref: '#/components/schemas/OIDCProvider'
|
||||
repository:
|
||||
type: string
|
||||
description: |
|
||||
GitHub repository in "org/repo" format.
|
||||
Only workflows running in this repository may exchange tokens.
|
||||
pattern: '^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$'
|
||||
example: "acme-corp/agent-deployer"
|
||||
branch:
|
||||
type: string
|
||||
nullable: true
|
||||
description: |
|
||||
Optional branch constraint. When set, only the specified branch may
|
||||
exchange tokens. When null, any branch in the repository is allowed.
|
||||
example: "main"
|
||||
agentId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: UUID of the agent this trust policy grants access to.
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-03-01T08:00:00.000Z"
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-03-28T11:30:00.000Z"
|
||||
|
||||
CreateTrustPolicyRequest:
|
||||
type: object
|
||||
description: Request body for registering a new OIDC trust policy.
|
||||
required:
|
||||
- provider
|
||||
- repository
|
||||
- agentId
|
||||
properties:
|
||||
provider:
|
||||
$ref: '#/components/schemas/OIDCProvider'
|
||||
repository:
|
||||
type: string
|
||||
description: |
|
||||
GitHub repository in "org/repo" format. Case-sensitive.
|
||||
Only workflows in this repository may use this trust policy.
|
||||
pattern: '^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$'
|
||||
example: "acme-corp/agent-deployer"
|
||||
branch:
|
||||
type: string
|
||||
description: |
|
||||
Optional branch constraint. When omitted, any branch in the repository is permitted.
|
||||
Recommended to set this to `main` for production trust policies.
|
||||
example: "main"
|
||||
agentId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: |
|
||||
UUID of the agent to grant access to.
|
||||
The agent must be registered and active in the same organization.
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "TRUST_POLICY_NOT_FOUND"
|
||||
message:
|
||||
type: string
|
||||
example: "Trust policy with the specified ID was not found."
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Missing or invalid Bearer token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "UNAUTHORIZED"
|
||||
message: "A valid Bearer token is required to access this resource."
|
||||
|
||||
Forbidden:
|
||||
description: Valid token but insufficient permissions.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "FORBIDDEN"
|
||||
message: "You do not have permission to perform this action."
|
||||
|
||||
NotFound:
|
||||
description: Trust policy not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "TRUST_POLICY_NOT_FOUND"
|
||||
message: "Trust policy with the specified ID was not found."
|
||||
|
||||
InternalServerError:
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
paths:
|
||||
/oidc/trust-policies:
|
||||
post:
|
||||
operationId: createOIDCTrustPolicy
|
||||
tags:
|
||||
- OIDC Trust Policies
|
||||
summary: Create an OIDC trust policy
|
||||
description: |
|
||||
Registers a new OIDC trust policy.
|
||||
|
||||
Once created, workflows running in the specified GitHub repository
|
||||
(and optionally matching the specified branch) can exchange their
|
||||
GitHub OIDC JWT for a SentryAgent.ai access token via `POST /oidc/token`.
|
||||
|
||||
A trust policy is organization-scoped — the agent referenced by `agentId`
|
||||
must belong to the same organization as the authenticated caller.
|
||||
|
||||
Requires a valid Bearer JWT (minimum `agents:write` scope recommended).
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateTrustPolicyRequest'
|
||||
example:
|
||||
provider: "github"
|
||||
repository: "acme-corp/agent-deployer"
|
||||
branch: "main"
|
||||
agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
responses:
|
||||
'201':
|
||||
description: Trust policy created successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OIDCTrustPolicy'
|
||||
example:
|
||||
id: "tp-abcd-1234-5678-ef01"
|
||||
provider: "github"
|
||||
repository: "acme-corp/agent-deployer"
|
||||
branch: "main"
|
||||
agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
createdAt: "2026-04-07T09:00:00.000Z"
|
||||
updatedAt: "2026-04-07T09:00:00.000Z"
|
||||
'400':
|
||||
description: Validation error in request body.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
examples:
|
||||
invalidProvider:
|
||||
summary: Unsupported OIDC provider
|
||||
value:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Request validation failed."
|
||||
details:
|
||||
field: "provider"
|
||||
reason: "Only 'github' is supported."
|
||||
invalidRepository:
|
||||
summary: Invalid repository format
|
||||
value:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Request validation failed."
|
||||
details:
|
||||
field: "repository"
|
||||
reason: "Must be in 'org/repo' format."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
description: Referenced agent not found in this organization.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "AGENT_NOT_FOUND"
|
||||
message: "Agent with the specified ID was not found."
|
||||
'409':
|
||||
description: A trust policy for this provider/repository/branch combination already exists.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "TRUST_POLICY_CONFLICT"
|
||||
message: "A trust policy for this repository and branch already exists."
|
||||
details:
|
||||
provider: "github"
|
||||
repository: "acme-corp/agent-deployer"
|
||||
branch: "main"
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
get:
|
||||
operationId: listOIDCTrustPolicies
|
||||
tags:
|
||||
- OIDC Trust Policies
|
||||
summary: List OIDC trust policies
|
||||
description: |
|
||||
Returns all trust policies for the authenticated organization,
|
||||
optionally filtered by the `agentId` query parameter.
|
||||
|
||||
Requires a valid Bearer JWT.
|
||||
parameters:
|
||||
- name: agentId
|
||||
in: query
|
||||
required: false
|
||||
description: Filter trust policies linked to a specific agent UUID.
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
responses:
|
||||
'200':
|
||||
description: List of trust policies returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/OIDCTrustPolicy'
|
||||
example:
|
||||
- id: "tp-abcd-1234-5678-ef01"
|
||||
provider: "github"
|
||||
repository: "acme-corp/agent-deployer"
|
||||
branch: "main"
|
||||
agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
createdAt: "2026-03-01T08:00:00.000Z"
|
||||
updatedAt: "2026-03-01T08:00:00.000Z"
|
||||
- id: "tp-efgh-5678-1234-ab01"
|
||||
provider: "github"
|
||||
repository: "acme-corp/inference-runner"
|
||||
branch: null
|
||||
agentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||||
createdAt: "2026-03-15T10:00:00.000Z"
|
||||
updatedAt: "2026-03-15T10:00:00.000Z"
|
||||
'400':
|
||||
description: Invalid query parameters.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Invalid query parameter value."
|
||||
details:
|
||||
field: "agentId"
|
||||
reason: "Must be a valid UUID."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/oidc/trust-policies/{id}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: UUID of the trust policy to delete.
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "tp-abcd-1234-5678-ef01"
|
||||
|
||||
delete:
|
||||
operationId: deleteOIDCTrustPolicy
|
||||
tags:
|
||||
- OIDC Trust Policies
|
||||
summary: Delete an OIDC trust policy
|
||||
description: |
|
||||
Permanently deletes an OIDC trust policy.
|
||||
|
||||
After deletion, workflows that previously used this policy to exchange
|
||||
GitHub OIDC tokens will receive `403 Forbidden` from `POST /oidc/token`.
|
||||
|
||||
Requires a valid Bearer JWT.
|
||||
responses:
|
||||
'204':
|
||||
description: Trust policy deleted successfully. No response body.
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
410
docs/openapi/oidc-wellknown.yaml
Normal file
410
docs/openapi/oidc-wellknown.yaml
Normal file
@@ -0,0 +1,410 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — OIDC Well-Known & Agent Info
|
||||
version: 1.0.0
|
||||
description: |
|
||||
OpenID Connect discovery, JWKS, and agent identity endpoints for the
|
||||
SentryAgent.ai AgentIdP platform.
|
||||
|
||||
**Unauthenticated endpoints** (public metadata):
|
||||
- `GET /.well-known/openid-configuration` — OIDC Discovery Document
|
||||
- `GET /.well-known/jwks.json` — JSON Web Key Set (public signing keys)
|
||||
|
||||
**Authenticated endpoint** (requires Bearer JWT):
|
||||
- `GET /agent-info` — Agent identity claims (equivalent to OIDC UserInfo)
|
||||
|
||||
All endpoints are mounted at the application root (`/`) so that
|
||||
`/.well-known/*` paths resolve correctly without an `/api/v1` prefix.
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: OIDC Discovery
|
||||
description: Public OIDC metadata endpoints (unauthenticated)
|
||||
- name: Agent Info
|
||||
description: Authenticated agent identity endpoint
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
JWT access token obtained via `POST /api/v1/token`.
|
||||
Include as `Authorization: Bearer <token>`.
|
||||
|
||||
schemas:
|
||||
OIDCDiscoveryDocument:
|
||||
type: object
|
||||
description: |
|
||||
OpenID Connect Discovery 1.0 document as defined in the OIDC specification.
|
||||
Returned by `GET /.well-known/openid-configuration`.
|
||||
required:
|
||||
- issuer
|
||||
- authorization_endpoint
|
||||
- token_endpoint
|
||||
- jwks_uri
|
||||
- response_types_supported
|
||||
- subject_types_supported
|
||||
- id_token_signing_alg_values_supported
|
||||
- scopes_supported
|
||||
- claims_supported
|
||||
- grant_types_supported
|
||||
properties:
|
||||
issuer:
|
||||
type: string
|
||||
format: uri
|
||||
description: OIDC Issuer URL. Must match the `iss` claim in ID tokens.
|
||||
example: "https://idp.sentryagent.ai"
|
||||
authorization_endpoint:
|
||||
type: string
|
||||
format: uri
|
||||
description: Authorization endpoint (stub — not implemented; client_credentials only).
|
||||
example: "https://idp.sentryagent.ai/oauth2/authorize"
|
||||
token_endpoint:
|
||||
type: string
|
||||
format: uri
|
||||
description: Token endpoint for the client_credentials grant.
|
||||
example: "https://idp.sentryagent.ai/oauth2/token"
|
||||
jwks_uri:
|
||||
type: string
|
||||
format: uri
|
||||
description: JWKS endpoint for ID token verification public keys.
|
||||
example: "https://idp.sentryagent.ai/.well-known/jwks.json"
|
||||
response_types_supported:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Supported response types.
|
||||
example: ["token", "id_token"]
|
||||
subject_types_supported:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Supported subject types.
|
||||
example: ["public"]
|
||||
id_token_signing_alg_values_supported:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Supported ID token signing algorithms.
|
||||
example: ["RS256", "ES256"]
|
||||
scopes_supported:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Supported OAuth 2.0 scopes.
|
||||
example: ["openid", "agents:read", "agents:write", "tokens:read", "audit:read", "admin:orgs"]
|
||||
claims_supported:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Claims that may appear in ID tokens or the agent-info response.
|
||||
example: ["sub", "iss", "aud", "iat", "exp", "agent_type", "deployment_env", "organization_id", "did"]
|
||||
grant_types_supported:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Supported grant types.
|
||||
example: ["client_credentials"]
|
||||
|
||||
JWKSKey:
|
||||
type: object
|
||||
description: A single JSON Web Key. Supports RSA (RS256) and EC P-256 (ES256) keys.
|
||||
required:
|
||||
- kid
|
||||
- kty
|
||||
- use
|
||||
- alg
|
||||
properties:
|
||||
kid:
|
||||
type: string
|
||||
description: Key ID — matches the `kid` header in signed JWTs.
|
||||
example: "key-20260328-001"
|
||||
kty:
|
||||
type: string
|
||||
description: Key type.
|
||||
enum:
|
||||
- RSA
|
||||
- EC
|
||||
example: "RSA"
|
||||
use:
|
||||
type: string
|
||||
description: Intended use. Always `sig` for signing keys.
|
||||
example: "sig"
|
||||
alg:
|
||||
type: string
|
||||
description: Algorithm.
|
||||
enum:
|
||||
- RS256
|
||||
- ES256
|
||||
example: "RS256"
|
||||
n:
|
||||
type: string
|
||||
description: RSA — Base64url-encoded modulus.
|
||||
example: "sI3P8XVb..."
|
||||
e:
|
||||
type: string
|
||||
description: RSA — Base64url-encoded public exponent.
|
||||
example: "AQAB"
|
||||
crv:
|
||||
type: string
|
||||
description: EC — Curve name.
|
||||
example: "P-256"
|
||||
x:
|
||||
type: string
|
||||
description: EC — Base64url-encoded x coordinate.
|
||||
example: "f83OJ3D..."
|
||||
y:
|
||||
type: string
|
||||
description: EC — Base64url-encoded y coordinate.
|
||||
example: "x_FEzRu..."
|
||||
|
||||
JWKSResponse:
|
||||
type: object
|
||||
description: JSON Web Key Set returned by `GET /.well-known/jwks.json`.
|
||||
required:
|
||||
- keys
|
||||
properties:
|
||||
keys:
|
||||
type: array
|
||||
description: |
|
||||
Array of JSON Web Keys. Includes all non-expired keys to support
|
||||
key rotation grace periods.
|
||||
items:
|
||||
$ref: '#/components/schemas/JWKSKey'
|
||||
|
||||
AgentInfoResponse:
|
||||
type: object
|
||||
description: |
|
||||
Agent identity claims for the authenticated caller.
|
||||
Analogous to the OIDC UserInfo endpoint.
|
||||
required:
|
||||
- sub
|
||||
- agent_type
|
||||
- deployment_env
|
||||
- organization_id
|
||||
- scope
|
||||
properties:
|
||||
sub:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Agent UUID (subject).
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
agent_type:
|
||||
type: string
|
||||
description: Functional classification of the agent.
|
||||
example: "screener"
|
||||
deployment_env:
|
||||
type: string
|
||||
description: Target deployment environment of the agent.
|
||||
example: "production"
|
||||
organization_id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Organization UUID the agent belongs to.
|
||||
example: "org-1234-5678-abcd-ef01"
|
||||
did:
|
||||
type: string
|
||||
description: W3C DID identifier for the agent, if one has been generated.
|
||||
example: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
scope:
|
||||
type: string
|
||||
description: OAuth 2.0 scope associated with the Bearer token used to call this endpoint.
|
||||
example: "agents:read agents:write"
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "UNAUTHORIZED"
|
||||
message:
|
||||
type: string
|
||||
example: "A valid Bearer token is required to access this resource."
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Missing or invalid Bearer token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "UNAUTHORIZED"
|
||||
message: "A valid Bearer token is required to access this resource."
|
||||
|
||||
NotFound:
|
||||
description: The requested resource was not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "AGENT_NOT_FOUND"
|
||||
message: "Agent with the specified ID was not found."
|
||||
|
||||
InternalServerError:
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
paths:
|
||||
/.well-known/openid-configuration:
|
||||
get:
|
||||
operationId: getOIDCDiscoveryDocument
|
||||
tags:
|
||||
- OIDC Discovery
|
||||
summary: OIDC Discovery Document
|
||||
description: |
|
||||
Returns the OpenID Connect Discovery 1.0 document for the SentryAgent.ai AgentIdP.
|
||||
|
||||
Consumers can fetch this document to auto-discover the token endpoint,
|
||||
JWKS URI, supported scopes, and supported claims — without hard-coding URLs.
|
||||
|
||||
This endpoint is **unauthenticated** and publicly cacheable.
|
||||
security: []
|
||||
responses:
|
||||
'200':
|
||||
description: OIDC Discovery Document returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OIDCDiscoveryDocument'
|
||||
example:
|
||||
issuer: "https://idp.sentryagent.ai"
|
||||
authorization_endpoint: "https://idp.sentryagent.ai/oauth2/authorize"
|
||||
token_endpoint: "https://idp.sentryagent.ai/oauth2/token"
|
||||
jwks_uri: "https://idp.sentryagent.ai/.well-known/jwks.json"
|
||||
response_types_supported:
|
||||
- token
|
||||
- id_token
|
||||
subject_types_supported:
|
||||
- public
|
||||
id_token_signing_alg_values_supported:
|
||||
- RS256
|
||||
- ES256
|
||||
scopes_supported:
|
||||
- openid
|
||||
- agents:read
|
||||
- agents:write
|
||||
- tokens:read
|
||||
- audit:read
|
||||
- admin:orgs
|
||||
claims_supported:
|
||||
- sub
|
||||
- iss
|
||||
- aud
|
||||
- iat
|
||||
- exp
|
||||
- agent_type
|
||||
- deployment_env
|
||||
- organization_id
|
||||
- did
|
||||
grant_types_supported:
|
||||
- client_credentials
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/.well-known/jwks.json:
|
||||
get:
|
||||
operationId: getJWKS
|
||||
tags:
|
||||
- OIDC Discovery
|
||||
summary: JSON Web Key Set (public signing keys)
|
||||
description: |
|
||||
Returns the JSON Web Key Set (JWKS) containing all active public keys
|
||||
used to sign ID tokens. Consumers use this to verify ID token signatures.
|
||||
|
||||
All non-expired keys are returned to support key rotation grace periods —
|
||||
a token signed with a recently-rotated key will still verify correctly
|
||||
if the old key appears in the JWKS response.
|
||||
|
||||
**Cache-Control:** `public, max-age=3600` — responses may be cached for up to 1 hour.
|
||||
|
||||
This endpoint is **unauthenticated**.
|
||||
security: []
|
||||
responses:
|
||||
'200':
|
||||
description: JWKS returned successfully.
|
||||
headers:
|
||||
Cache-Control:
|
||||
schema:
|
||||
type: string
|
||||
description: Caching directive. Always `public, max-age=3600`.
|
||||
example: "public, max-age=3600"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/JWKSResponse'
|
||||
example:
|
||||
keys:
|
||||
- kid: "key-20260328-001"
|
||||
kty: "RSA"
|
||||
use: "sig"
|
||||
alg: "RS256"
|
||||
n: "sI3P8XVb..."
|
||||
e: "AQAB"
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/agent-info:
|
||||
get:
|
||||
operationId: getAgentInfo
|
||||
tags:
|
||||
- Agent Info
|
||||
summary: Get authenticated agent identity claims
|
||||
description: |
|
||||
Returns identity claims for the agent authenticated by the provided Bearer token.
|
||||
Equivalent to the OIDC UserInfo endpoint — returns the agent's type,
|
||||
deployment environment, organization, DID (if generated), and active scopes.
|
||||
|
||||
The agent UUID is read from the `sub` claim of the Bearer token.
|
||||
Requires a valid Bearer JWT.
|
||||
security:
|
||||
- BearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: Agent identity claims returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AgentInfoResponse'
|
||||
example:
|
||||
sub: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
agent_type: "screener"
|
||||
deployment_env: "production"
|
||||
organization_id: "org-1234-5678-abcd-ef01"
|
||||
did: "did:web:api.sentryagent.ai:agents:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
scope: "agents:read agents:write"
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'404':
|
||||
description: The agent referenced in the token no longer exists.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "AGENT_NOT_FOUND"
|
||||
message: "Agent with the specified ID was not found."
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
707
docs/openapi/organizations.yaml
Normal file
707
docs/openapi/organizations.yaml
Normal file
@@ -0,0 +1,707 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — Organizations (Multi-Tenancy)
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Organization (tenant) management endpoints for the SentryAgent.ai AgentIdP platform.
|
||||
|
||||
Organizations are the top-level multi-tenancy boundary. Each organization has its
|
||||
own pool of registered agents, plan-tier limits, and billing context.
|
||||
|
||||
**All endpoints require a valid Bearer JWT** with appropriate OPA-enforced scope.
|
||||
|
||||
**Plan Tiers:**
|
||||
| Tier | Max Agents | Max Tokens/Month |
|
||||
|------|-----------|-----------------|
|
||||
| `free` | 100 | 10,000 |
|
||||
| `pro` | 1,000 | 100,000 |
|
||||
| `enterprise` | unlimited | unlimited |
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000/api/v1
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai/v1
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: Organizations
|
||||
description: CRUD operations for organizations (tenants)
|
||||
- name: Organization Members
|
||||
description: Agent membership management within organizations
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
JWT access token obtained via `POST /token`.
|
||||
Include as `Authorization: Bearer <token>`.
|
||||
|
||||
schemas:
|
||||
PlanTier:
|
||||
type: string
|
||||
enum:
|
||||
- free
|
||||
- pro
|
||||
- enterprise
|
||||
description: Subscription plan tier for an organization.
|
||||
example: pro
|
||||
|
||||
OrgStatus:
|
||||
type: string
|
||||
enum:
|
||||
- active
|
||||
- suspended
|
||||
- deleted
|
||||
description: Lifecycle status of an organization.
|
||||
example: active
|
||||
|
||||
OrgRole:
|
||||
type: string
|
||||
enum:
|
||||
- member
|
||||
- admin
|
||||
description: Role of an agent within an organization.
|
||||
example: member
|
||||
|
||||
Organization:
|
||||
type: object
|
||||
description: Full representation of a registered organization (tenant).
|
||||
required:
|
||||
- organizationId
|
||||
- name
|
||||
- slug
|
||||
- planTier
|
||||
- maxAgents
|
||||
- maxTokensPerMonth
|
||||
- status
|
||||
- createdAt
|
||||
- updatedAt
|
||||
properties:
|
||||
organizationId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Immutable, system-assigned unique identifier for the organization.
|
||||
readOnly: true
|
||||
example: "org-1234-5678-abcd-ef01"
|
||||
name:
|
||||
type: string
|
||||
description: Human-readable display name of the organization.
|
||||
minLength: 1
|
||||
maxLength: 256
|
||||
example: "Acme Corp"
|
||||
slug:
|
||||
type: string
|
||||
description: |
|
||||
URL-friendly unique identifier. Lowercase letters, digits, and hyphens only.
|
||||
Immutable after creation.
|
||||
pattern: '^[a-z0-9-]+$'
|
||||
example: "acme-corp"
|
||||
planTier:
|
||||
$ref: '#/components/schemas/PlanTier'
|
||||
maxAgents:
|
||||
type: integer
|
||||
description: Maximum number of agents permitted in this organization.
|
||||
minimum: 1
|
||||
example: 100
|
||||
maxTokensPerMonth:
|
||||
type: integer
|
||||
description: Maximum OAuth 2.0 token requests per calendar month.
|
||||
minimum: 1
|
||||
example: 10000
|
||||
status:
|
||||
$ref: '#/components/schemas/OrgStatus'
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-03-01T08:00:00.000Z"
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-03-28T11:30:00.000Z"
|
||||
|
||||
CreateOrgRequest:
|
||||
type: object
|
||||
description: Request body for creating a new organization.
|
||||
required:
|
||||
- name
|
||||
- slug
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: Human-readable display name.
|
||||
minLength: 1
|
||||
maxLength: 256
|
||||
example: "Acme Corp"
|
||||
slug:
|
||||
type: string
|
||||
description: URL-friendly unique identifier. Lowercase letters, digits, hyphens only.
|
||||
pattern: '^[a-z0-9-]+$'
|
||||
minLength: 1
|
||||
maxLength: 64
|
||||
example: "acme-corp"
|
||||
planTier:
|
||||
$ref: '#/components/schemas/PlanTier'
|
||||
description: Defaults to `free` when omitted.
|
||||
maxAgents:
|
||||
type: integer
|
||||
description: Override the default max agents for this plan tier.
|
||||
minimum: 1
|
||||
example: 100
|
||||
maxTokensPerMonth:
|
||||
type: integer
|
||||
description: Override the default max tokens per month.
|
||||
minimum: 1
|
||||
example: 10000
|
||||
|
||||
UpdateOrgRequest:
|
||||
type: object
|
||||
description: |
|
||||
Request body for partially updating an organization.
|
||||
All fields are optional; only provided fields are updated.
|
||||
`status` may only be set to `active` or `suspended` via this endpoint;
|
||||
`deleted` is applied only through the DELETE operation.
|
||||
minProperties: 1
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 256
|
||||
example: "Acme Corporation"
|
||||
planTier:
|
||||
$ref: '#/components/schemas/PlanTier'
|
||||
maxAgents:
|
||||
type: integer
|
||||
minimum: 1
|
||||
example: 1000
|
||||
maxTokensPerMonth:
|
||||
type: integer
|
||||
minimum: 1
|
||||
example: 100000
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- active
|
||||
- suspended
|
||||
description: Set to `active` to reactivate a suspended org, or `suspended` to disable it.
|
||||
example: active
|
||||
|
||||
PaginatedOrgsResponse:
|
||||
type: object
|
||||
description: Paginated list of organizations.
|
||||
required:
|
||||
- data
|
||||
- total
|
||||
- page
|
||||
- limit
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Organization'
|
||||
total:
|
||||
type: integer
|
||||
example: 42
|
||||
page:
|
||||
type: integer
|
||||
example: 1
|
||||
limit:
|
||||
type: integer
|
||||
example: 20
|
||||
|
||||
OrgMember:
|
||||
type: object
|
||||
description: An agent's membership record within an organization.
|
||||
required:
|
||||
- memberId
|
||||
- organizationId
|
||||
- agentId
|
||||
- role
|
||||
- joinedAt
|
||||
properties:
|
||||
memberId:
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
example: "mem-abcd-1234-5678-ef01"
|
||||
organizationId:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "org-1234-5678-abcd-ef01"
|
||||
agentId:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
role:
|
||||
$ref: '#/components/schemas/OrgRole'
|
||||
joinedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-03-28T09:00:00.000Z"
|
||||
|
||||
AddMemberRequest:
|
||||
type: object
|
||||
description: Request body for adding an agent to an organization.
|
||||
required:
|
||||
- agentId
|
||||
- role
|
||||
properties:
|
||||
agentId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: UUID of the agent to add.
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
role:
|
||||
$ref: '#/components/schemas/OrgRole'
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "VALIDATION_ERROR"
|
||||
message:
|
||||
type: string
|
||||
example: "Request validation failed."
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Missing or invalid Bearer token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "UNAUTHORIZED"
|
||||
message: "A valid Bearer token is required to access this resource."
|
||||
|
||||
Forbidden:
|
||||
description: Valid token but insufficient permissions.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "FORBIDDEN"
|
||||
message: "You do not have permission to perform this action."
|
||||
|
||||
NotFound:
|
||||
description: Organization not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "ORG_NOT_FOUND"
|
||||
message: "Organization with the specified ID was not found."
|
||||
|
||||
TooManyRequests:
|
||||
description: Rate limit exceeded.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "RATE_LIMIT_EXCEEDED"
|
||||
message: "Too many requests. Please retry after the rate limit window resets."
|
||||
|
||||
InternalServerError:
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
paths:
|
||||
/organizations:
|
||||
post:
|
||||
operationId: createOrganization
|
||||
tags:
|
||||
- Organizations
|
||||
summary: Create a new organization
|
||||
description: |
|
||||
Creates a new organization (tenant). The `slug` must be unique across all organizations
|
||||
and is immutable after creation.
|
||||
|
||||
The requesting agent's organization context is determined from the Bearer token.
|
||||
Requires `admin:orgs` scope.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateOrgRequest'
|
||||
example:
|
||||
name: "Acme Corp"
|
||||
slug: "acme-corp"
|
||||
planTier: "pro"
|
||||
maxAgents: 500
|
||||
maxTokensPerMonth: 50000
|
||||
responses:
|
||||
'201':
|
||||
description: Organization created successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Organization'
|
||||
example:
|
||||
organizationId: "org-1234-5678-abcd-ef01"
|
||||
name: "Acme Corp"
|
||||
slug: "acme-corp"
|
||||
planTier: "pro"
|
||||
maxAgents: 500
|
||||
maxTokensPerMonth: 50000
|
||||
status: "active"
|
||||
createdAt: "2026-04-07T09:00:00.000Z"
|
||||
updatedAt: "2026-04-07T09:00:00.000Z"
|
||||
'400':
|
||||
description: Validation error in request body.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Request validation failed."
|
||||
details:
|
||||
field: "slug"
|
||||
reason: "slug must contain only lowercase letters, digits, and hyphens."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'409':
|
||||
description: An organization with the provided slug already exists.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "ORG_SLUG_CONFLICT"
|
||||
message: "An organization with this slug already exists."
|
||||
details:
|
||||
slug: "acme-corp"
|
||||
'429':
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
get:
|
||||
operationId: listOrganizations
|
||||
tags:
|
||||
- Organizations
|
||||
summary: List organizations
|
||||
description: |
|
||||
Returns a paginated list of organizations. Results can be filtered by `status`.
|
||||
Results are ordered by `createdAt` descending.
|
||||
|
||||
Requires `admin:orgs` scope.
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
default: 1
|
||||
example: 1
|
||||
- name: limit
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
default: 20
|
||||
example: 20
|
||||
- name: status
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
$ref: '#/components/schemas/OrgStatus'
|
||||
responses:
|
||||
'200':
|
||||
description: Paginated list of organizations returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PaginatedOrgsResponse'
|
||||
example:
|
||||
data:
|
||||
- organizationId: "org-1234-5678-abcd-ef01"
|
||||
name: "Acme Corp"
|
||||
slug: "acme-corp"
|
||||
planTier: "pro"
|
||||
maxAgents: 500
|
||||
maxTokensPerMonth: 50000
|
||||
status: "active"
|
||||
createdAt: "2026-03-01T08:00:00.000Z"
|
||||
updatedAt: "2026-03-28T11:30:00.000Z"
|
||||
total: 42
|
||||
page: 1
|
||||
limit: 20
|
||||
'400':
|
||||
description: Invalid query parameters.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Invalid query parameter value."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'429':
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/organizations/{orgId}:
|
||||
parameters:
|
||||
- name: orgId
|
||||
in: path
|
||||
required: true
|
||||
description: The unique UUID identifier of the organization.
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "org-1234-5678-abcd-ef01"
|
||||
|
||||
get:
|
||||
operationId: getOrganization
|
||||
tags:
|
||||
- Organizations
|
||||
summary: Get organization by ID
|
||||
description: |
|
||||
Retrieves the full record for a single organization.
|
||||
Requires `admin:orgs` scope.
|
||||
responses:
|
||||
'200':
|
||||
description: Organization record returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Organization'
|
||||
example:
|
||||
organizationId: "org-1234-5678-abcd-ef01"
|
||||
name: "Acme Corp"
|
||||
slug: "acme-corp"
|
||||
planTier: "pro"
|
||||
maxAgents: 500
|
||||
maxTokensPerMonth: 50000
|
||||
status: "active"
|
||||
createdAt: "2026-03-01T08:00:00.000Z"
|
||||
updatedAt: "2026-03-28T11:30:00.000Z"
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
patch:
|
||||
operationId: updateOrganization
|
||||
tags:
|
||||
- Organizations
|
||||
summary: Update organization metadata
|
||||
description: |
|
||||
Partially updates an organization's metadata.
|
||||
Only provided fields are updated; omitted fields are unchanged.
|
||||
`slug` and `organizationId` are immutable.
|
||||
Requires `admin:orgs` scope.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UpdateOrgRequest'
|
||||
example:
|
||||
name: "Acme Corporation"
|
||||
planTier: "enterprise"
|
||||
responses:
|
||||
'200':
|
||||
description: Organization updated successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Organization'
|
||||
example:
|
||||
organizationId: "org-1234-5678-abcd-ef01"
|
||||
name: "Acme Corporation"
|
||||
slug: "acme-corp"
|
||||
planTier: "enterprise"
|
||||
maxAgents: 500
|
||||
maxTokensPerMonth: 50000
|
||||
status: "active"
|
||||
createdAt: "2026-03-01T08:00:00.000Z"
|
||||
updatedAt: "2026-04-07T09:00:00.000Z"
|
||||
'400':
|
||||
description: Validation error or attempt to modify an immutable field.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Request validation failed."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'429':
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
delete:
|
||||
operationId: deleteOrganization
|
||||
tags:
|
||||
- Organizations
|
||||
summary: Soft-delete an organization
|
||||
description: |
|
||||
Permanently soft-deletes an organization by setting its status to `deleted`.
|
||||
The record is retained for audit purposes.
|
||||
|
||||
**Effects:**
|
||||
- All agents within the organization are suspended.
|
||||
- No new tokens may be issued for agents in this organization.
|
||||
- This operation is **irreversible** via the API.
|
||||
|
||||
Requires `admin:orgs` scope.
|
||||
responses:
|
||||
'204':
|
||||
description: Organization soft-deleted successfully. No response body.
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'409':
|
||||
description: Organization is already deleted.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "ORG_ALREADY_DELETED"
|
||||
message: "This organization has already been deleted."
|
||||
'429':
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/organizations/{orgId}/members:
|
||||
parameters:
|
||||
- name: orgId
|
||||
in: path
|
||||
required: true
|
||||
description: The unique UUID identifier of the organization.
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "org-1234-5678-abcd-ef01"
|
||||
|
||||
post:
|
||||
operationId: addOrganizationMember
|
||||
tags:
|
||||
- Organization Members
|
||||
summary: Add an agent as a member of an organization
|
||||
description: |
|
||||
Adds a registered agent to an organization with the specified role.
|
||||
The agent must already be registered in the system.
|
||||
|
||||
Requires `admin:orgs` scope.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AddMemberRequest'
|
||||
example:
|
||||
agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
role: "member"
|
||||
responses:
|
||||
'201':
|
||||
description: Agent added to organization successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OrgMember'
|
||||
example:
|
||||
memberId: "mem-abcd-1234-5678-ef01"
|
||||
organizationId: "org-1234-5678-abcd-ef01"
|
||||
agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
role: "member"
|
||||
joinedAt: "2026-04-07T09:00:00.000Z"
|
||||
'400':
|
||||
description: Validation error in request body.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Request validation failed."
|
||||
details:
|
||||
field: "role"
|
||||
reason: "role must be one of: member, admin."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
description: Organization or agent not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
examples:
|
||||
orgNotFound:
|
||||
summary: Organization not found
|
||||
value:
|
||||
code: "ORG_NOT_FOUND"
|
||||
message: "Organization with the specified ID was not found."
|
||||
agentNotFound:
|
||||
summary: Agent not found
|
||||
value:
|
||||
code: "AGENT_NOT_FOUND"
|
||||
message: "Agent with the specified ID was not found."
|
||||
'409':
|
||||
description: Agent is already a member of this organization.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "ALREADY_MEMBER"
|
||||
message: "The agent is already a member of this organization."
|
||||
'429':
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
257
docs/openapi/scaffold.yaml
Normal file
257
docs/openapi/scaffold.yaml
Normal file
@@ -0,0 +1,257 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — SDK Scaffold Generator
|
||||
version: 1.0.0
|
||||
description: |
|
||||
SDK scaffold generator endpoint for the SentryAgent.ai AgentIdP platform.
|
||||
|
||||
The scaffold endpoint generates a ready-to-run agent project ZIP archive
|
||||
pre-configured with the agent's credentials, API URL, and chosen SDK.
|
||||
|
||||
The generated scaffold includes:
|
||||
- Language-specific project structure (TypeScript / Python / Go / Java / Rust)
|
||||
- Pre-filled `.env.example` with `CLIENT_ID` and `API_URL`
|
||||
- Agent authentication boilerplate using the SentryAgent.ai SDK
|
||||
- README with quickstart instructions
|
||||
- Docker / CI configuration (where applicable)
|
||||
|
||||
**Authentication:** Requires a valid Bearer JWT.
|
||||
**Rate limit:** 10 requests per minute per tenant (separate from the global limit).
|
||||
The scaffold endpoint responds with `Retry-After` on rate limit.
|
||||
|
||||
**Supported languages:** `typescript`, `python`, `go`, `java`, `rust`
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000/api/v1
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai/v1
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: SDK Scaffold
|
||||
description: Generate a ready-to-run agent SDK project scaffold
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
JWT access token obtained via `POST /token`.
|
||||
Include as `Authorization: Bearer <token>`.
|
||||
|
||||
schemas:
|
||||
ScaffoldLanguage:
|
||||
type: string
|
||||
enum:
|
||||
- typescript
|
||||
- python
|
||||
- go
|
||||
- java
|
||||
- rust
|
||||
description: Target programming language for the scaffold project.
|
||||
example: typescript
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "AGENT_NOT_FOUND"
|
||||
message:
|
||||
type: string
|
||||
example: "Agent with the specified ID was not found."
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Missing or invalid Bearer token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "UNAUTHORIZED"
|
||||
message: "A valid Bearer token is required to access this resource."
|
||||
|
||||
Forbidden:
|
||||
description: Valid token but insufficient permissions.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "FORBIDDEN"
|
||||
message: "You do not have permission to access this agent's scaffold."
|
||||
|
||||
NotFound:
|
||||
description: Agent not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "AGENT_NOT_FOUND"
|
||||
message: "Agent with the specified ID was not found."
|
||||
|
||||
TooManyRequests:
|
||||
description: |
|
||||
Scaffold-specific rate limit exceeded (10 requests per minute per tenant).
|
||||
Retry after the duration specified in the `Retry-After` header.
|
||||
headers:
|
||||
Retry-After:
|
||||
schema:
|
||||
type: integer
|
||||
description: Number of seconds to wait before retrying.
|
||||
example: 60
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "RATE_LIMIT_EXCEEDED"
|
||||
message: "Scaffold rate limit exceeded. Please retry after 60 seconds."
|
||||
|
||||
InternalServerError:
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
paths:
|
||||
/sdk/scaffold/{agentId}:
|
||||
get:
|
||||
operationId: getAgentScaffold
|
||||
tags:
|
||||
- SDK Scaffold
|
||||
summary: Generate and download an agent SDK scaffold ZIP
|
||||
description: |
|
||||
Generates a ready-to-run agent project scaffold for the specified agent
|
||||
and streams it as a ZIP file download.
|
||||
|
||||
The scaffold is customized with:
|
||||
- The agent's `CLIENT_ID` pre-filled in `.env.example`
|
||||
- The platform API URL (`API_URL`) configured for the environment
|
||||
- The agent's name, type, and capabilities reflected in project metadata
|
||||
- Language-specific SDK integration boilerplate
|
||||
|
||||
**Rate limit:** 10 requests per minute per tenant.
|
||||
Exceeding this limit returns `429 Too Many Requests` with a `Retry-After` header.
|
||||
|
||||
**Language selection:** Pass the desired `language` query parameter.
|
||||
Defaults to `typescript` when omitted.
|
||||
|
||||
**Authorization:** The authenticated agent (from Bearer token) must belong to
|
||||
the same organization as the target agent (`agentId`). Cross-tenant scaffold
|
||||
generation is not permitted.
|
||||
parameters:
|
||||
- name: agentId
|
||||
in: path
|
||||
required: true
|
||||
description: UUID of the agent to generate a scaffold for.
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
- name: language
|
||||
in: query
|
||||
required: false
|
||||
description: |
|
||||
Target programming language for the scaffold.
|
||||
Defaults to `typescript` when omitted.
|
||||
schema:
|
||||
$ref: '#/components/schemas/ScaffoldLanguage'
|
||||
example: typescript
|
||||
responses:
|
||||
'200':
|
||||
description: |
|
||||
Scaffold ZIP archive generated and streamed successfully.
|
||||
|
||||
The response body is a ZIP archive containing the generated project files.
|
||||
Save it locally and unzip to get started:
|
||||
```
|
||||
curl -O -J -H "Authorization: Bearer <token>" \
|
||||
"https://api.sentryagent.ai/v1/sdk/scaffold/a1b2c3d4-e5f6-7890-abcd-ef1234567890?language=typescript"
|
||||
unzip screener-001-scaffold.zip
|
||||
cd screener-001-scaffold && npm install
|
||||
```
|
||||
headers:
|
||||
Content-Disposition:
|
||||
schema:
|
||||
type: string
|
||||
description: |
|
||||
Filename for the ZIP download.
|
||||
Format: `attachment; filename="<agent-name>-scaffold.zip"`
|
||||
example: 'attachment; filename="screener-001-scaffold.zip"'
|
||||
Content-Type:
|
||||
schema:
|
||||
type: string
|
||||
example: "application/zip"
|
||||
X-RateLimit-Limit:
|
||||
schema:
|
||||
type: integer
|
||||
description: Scaffold rate limit (requests per minute per tenant).
|
||||
example: 10
|
||||
X-RateLimit-Remaining:
|
||||
schema:
|
||||
type: integer
|
||||
description: Remaining scaffold requests in the current window.
|
||||
example: 9
|
||||
content:
|
||||
application/zip:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
description: ZIP archive of the generated scaffold project.
|
||||
'400':
|
||||
description: Invalid `language` query parameter.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Invalid language. Supported languages are: typescript, python, go, java, rust."
|
||||
details:
|
||||
field: "language"
|
||||
provided: "ruby"
|
||||
supported:
|
||||
- typescript
|
||||
- python
|
||||
- go
|
||||
- java
|
||||
- rust
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'422':
|
||||
description: Agent is decommissioned — cannot generate scaffold for inactive agents.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "AGENT_DECOMMISSIONED"
|
||||
message: "Cannot generate scaffold for a decommissioned agent."
|
||||
'429':
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
341
docs/openapi/tiers.yaml
Normal file
341
docs/openapi/tiers.yaml
Normal file
@@ -0,0 +1,341 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — Tier Management
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Tier status and upgrade endpoints for the SentryAgent.ai AgentIdP platform.
|
||||
|
||||
The tier system defines per-organization limits on agents, tokens, and API calls.
|
||||
|
||||
**All endpoints require a valid Bearer JWT.**
|
||||
|
||||
**Available tiers:**
|
||||
| Tier | Max Agents | Max Tokens/Month | Rate Limit |
|
||||
|------|-----------|-----------------|------------|
|
||||
| `free` | 100 | 10,000 | 100 req/min |
|
||||
| `pro` | 1,000 | 100,000 | 1,000 req/min |
|
||||
| `enterprise` | unlimited | unlimited | custom |
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000/api/v1
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai/v1
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: Tiers
|
||||
description: Tier status and upgrade management
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
JWT access token obtained via `POST /token`.
|
||||
Include as `Authorization: Bearer <token>`.
|
||||
|
||||
schemas:
|
||||
PlanTier:
|
||||
type: string
|
||||
enum:
|
||||
- free
|
||||
- pro
|
||||
- enterprise
|
||||
description: Current subscription plan tier.
|
||||
example: free
|
||||
|
||||
TierLimits:
|
||||
type: object
|
||||
description: Hard limits defined by the current tier.
|
||||
required:
|
||||
- maxAgents
|
||||
- maxTokensPerMonth
|
||||
- rateLimitPerMinute
|
||||
properties:
|
||||
maxAgents:
|
||||
type: integer
|
||||
description: Maximum number of agents allowed for this organization.
|
||||
example: 100
|
||||
maxTokensPerMonth:
|
||||
type: integer
|
||||
description: Maximum OAuth 2.0 token requests per calendar month.
|
||||
example: 10000
|
||||
rateLimitPerMinute:
|
||||
type: integer
|
||||
description: Maximum API requests per minute.
|
||||
example: 100
|
||||
|
||||
TierUsage:
|
||||
type: object
|
||||
description: Live usage counters for the current billing period.
|
||||
required:
|
||||
- agentsRegistered
|
||||
- tokensThisMonth
|
||||
- requestsThisMinute
|
||||
properties:
|
||||
agentsRegistered:
|
||||
type: integer
|
||||
description: Current number of active (non-decommissioned) agents.
|
||||
minimum: 0
|
||||
example: 47
|
||||
tokensThisMonth:
|
||||
type: integer
|
||||
description: Total OAuth 2.0 tokens issued in the current calendar month.
|
||||
minimum: 0
|
||||
example: 3214
|
||||
requestsThisMinute:
|
||||
type: integer
|
||||
description: API requests in the current rate-limit window.
|
||||
minimum: 0
|
||||
example: 12
|
||||
|
||||
TierStatus:
|
||||
type: object
|
||||
description: Full tier status response — current tier, plan limits, and live usage.
|
||||
required:
|
||||
- organizationId
|
||||
- tier
|
||||
- limits
|
||||
- usage
|
||||
properties:
|
||||
organizationId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Organization the tier status applies to.
|
||||
example: "org-1234-5678-abcd-ef01"
|
||||
tier:
|
||||
$ref: '#/components/schemas/PlanTier'
|
||||
limits:
|
||||
$ref: '#/components/schemas/TierLimits'
|
||||
usage:
|
||||
$ref: '#/components/schemas/TierUsage'
|
||||
billingPeriodStart:
|
||||
type: string
|
||||
format: date
|
||||
description: First day of the current billing period (UTC).
|
||||
example: "2026-04-01"
|
||||
billingPeriodEnd:
|
||||
type: string
|
||||
format: date
|
||||
description: Last day of the current billing period (UTC).
|
||||
example: "2026-04-30"
|
||||
|
||||
TierUpgradeRequest:
|
||||
type: object
|
||||
description: Request body for initiating a tier upgrade.
|
||||
required:
|
||||
- targetTier
|
||||
properties:
|
||||
targetTier:
|
||||
type: string
|
||||
enum:
|
||||
- pro
|
||||
- enterprise
|
||||
description: |
|
||||
The target plan tier to upgrade to.
|
||||
Downgrading is not permitted via this endpoint.
|
||||
example: "pro"
|
||||
successUrl:
|
||||
type: string
|
||||
format: uri
|
||||
description: |
|
||||
URL to redirect to after successful payment.
|
||||
Defaults to the platform dashboard when omitted.
|
||||
example: "https://my-app.example.com/dashboard?upgrade=success"
|
||||
cancelUrl:
|
||||
type: string
|
||||
format: uri
|
||||
description: |
|
||||
URL to redirect to if the user cancels checkout.
|
||||
Defaults to the platform dashboard when omitted.
|
||||
example: "https://my-app.example.com/dashboard?upgrade=cancel"
|
||||
|
||||
TierUpgradeResponse:
|
||||
type: object
|
||||
description: Stripe Checkout URL to initiate the tier upgrade payment flow.
|
||||
required:
|
||||
- checkoutUrl
|
||||
- targetTier
|
||||
properties:
|
||||
checkoutUrl:
|
||||
type: string
|
||||
format: uri
|
||||
description: |
|
||||
Stripe-hosted Checkout page URL.
|
||||
Redirect the authenticated user to this URL to complete payment.
|
||||
example: "https://checkout.stripe.com/pay/cs_test_abcdef1234567890"
|
||||
targetTier:
|
||||
$ref: '#/components/schemas/PlanTier'
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "UNAUTHORIZED"
|
||||
message:
|
||||
type: string
|
||||
example: "A valid Bearer token is required to access this resource."
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Missing or invalid Bearer token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "UNAUTHORIZED"
|
||||
message: "A valid Bearer token is required to access this resource."
|
||||
|
||||
Forbidden:
|
||||
description: Valid token but insufficient permissions.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "FORBIDDEN"
|
||||
message: "You do not have permission to perform this action."
|
||||
|
||||
InternalServerError:
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
paths:
|
||||
/tiers/status:
|
||||
get:
|
||||
operationId: getTierStatus
|
||||
tags:
|
||||
- Tiers
|
||||
summary: Get current tier status
|
||||
description: |
|
||||
Returns the current tier, plan limits, and live usage counters for the
|
||||
authenticated organization.
|
||||
|
||||
The organization ID is derived from the `organization_id` claim in the
|
||||
Bearer JWT — no explicit organization ID is required in the request.
|
||||
|
||||
Use this endpoint to:
|
||||
- Display the current plan in your dashboard
|
||||
- Check remaining quota before performing bulk operations
|
||||
- Determine whether an upgrade is needed
|
||||
responses:
|
||||
'200':
|
||||
description: Tier status returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TierStatus'
|
||||
example:
|
||||
organizationId: "org-1234-5678-abcd-ef01"
|
||||
tier: "free"
|
||||
limits:
|
||||
maxAgents: 100
|
||||
maxTokensPerMonth: 10000
|
||||
rateLimitPerMinute: 100
|
||||
usage:
|
||||
agentsRegistered: 47
|
||||
tokensThisMonth: 3214
|
||||
requestsThisMinute: 12
|
||||
billingPeriodStart: "2026-04-01"
|
||||
billingPeriodEnd: "2026-04-30"
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/tiers/upgrade:
|
||||
post:
|
||||
operationId: initiateTierUpgrade
|
||||
tags:
|
||||
- Tiers
|
||||
summary: Initiate a tier upgrade via Stripe
|
||||
description: |
|
||||
Initiates a Stripe Checkout Session for upgrading the organization's plan tier.
|
||||
|
||||
The returned `checkoutUrl` is a Stripe-hosted payment page.
|
||||
Redirect the authenticated user to this URL to complete the upgrade payment.
|
||||
|
||||
After successful payment, Stripe notifies the platform via the
|
||||
`POST /billing/webhook` endpoint, which activates the new tier automatically.
|
||||
|
||||
**Constraints:**
|
||||
- Only upgrades are supported (free → pro, free → enterprise, pro → enterprise).
|
||||
- Attempting to "upgrade" to the current or lower tier returns `400`.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TierUpgradeRequest'
|
||||
example:
|
||||
targetTier: "pro"
|
||||
successUrl: "https://my-app.example.com/dashboard?upgrade=success"
|
||||
cancelUrl: "https://my-app.example.com/dashboard?upgrade=cancel"
|
||||
responses:
|
||||
'201':
|
||||
description: Stripe Checkout Session created successfully. Redirect user to checkoutUrl.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TierUpgradeResponse'
|
||||
example:
|
||||
checkoutUrl: "https://checkout.stripe.com/pay/cs_test_abcdef1234567890"
|
||||
targetTier: "pro"
|
||||
'400':
|
||||
description: Invalid upgrade request — already on target tier or attempting a downgrade.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
examples:
|
||||
alreadyOnTier:
|
||||
summary: Already on target tier
|
||||
value:
|
||||
code: "ALREADY_ON_TIER"
|
||||
message: "Your organization is already on the 'pro' tier."
|
||||
invalidDowngrade:
|
||||
summary: Downgrade not permitted
|
||||
value:
|
||||
code: "DOWNGRADE_NOT_PERMITTED"
|
||||
message: "Downgrading tiers is not supported via this endpoint."
|
||||
missingOrgId:
|
||||
summary: Missing organization_id in token
|
||||
value:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "organization_id is required in token."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'500':
|
||||
description: Unexpected error or Stripe API failure.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "STRIPE_ERROR"
|
||||
message: "Failed to create Stripe Checkout Session. Please try again."
|
||||
676
docs/openapi/webhooks.yaml
Normal file
676
docs/openapi/webhooks.yaml
Normal file
@@ -0,0 +1,676 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — Webhooks & Event Subscriptions
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Webhook subscription management and delivery history endpoints for the
|
||||
SentryAgent.ai AgentIdP platform.
|
||||
|
||||
Webhooks deliver real-time event notifications to registered HTTP endpoints
|
||||
when significant platform events occur (agent lifecycle changes, credential
|
||||
operations, token events).
|
||||
|
||||
**All endpoints require a valid Bearer JWT** with appropriate scope:
|
||||
- `webhooks:read` — required for GET operations
|
||||
- `webhooks:write` — required for POST, PATCH, DELETE operations
|
||||
|
||||
**Delivery mechanism:**
|
||||
- Events are delivered via `POST` to the registered `url`
|
||||
- Each delivery carries a signed JSON envelope (`X-SentryAgent-Signature` header)
|
||||
- Failed deliveries are retried with exponential backoff (up to 10 attempts)
|
||||
- After 10 failures the subscription is marked `dead_letter`
|
||||
|
||||
**Supported event types:**
|
||||
`agent.created`, `agent.updated`, `agent.suspended`, `agent.reactivated`,
|
||||
`agent.decommissioned`, `credential.generated`, `credential.rotated`,
|
||||
`credential.revoked`, `token.issued`, `token.revoked`
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000/api/v1
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai/v1
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: Webhook Subscriptions
|
||||
description: Create and manage webhook endpoint subscriptions
|
||||
- name: Webhook Deliveries
|
||||
description: Query delivery history and attempt records
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
JWT access token obtained via `POST /token`.
|
||||
Include as `Authorization: Bearer <token>`.
|
||||
|
||||
schemas:
|
||||
WebhookEventType:
|
||||
type: string
|
||||
enum:
|
||||
- agent.created
|
||||
- agent.updated
|
||||
- agent.suspended
|
||||
- agent.reactivated
|
||||
- agent.decommissioned
|
||||
- credential.generated
|
||||
- credential.rotated
|
||||
- credential.revoked
|
||||
- token.issued
|
||||
- token.revoked
|
||||
description: Platform event type that can be subscribed to.
|
||||
example: agent.created
|
||||
|
||||
WebhookDeliveryStatus:
|
||||
type: string
|
||||
enum:
|
||||
- pending
|
||||
- delivered
|
||||
- failed
|
||||
- dead_letter
|
||||
description: |
|
||||
Current status of a delivery attempt.
|
||||
- `pending` — queued for delivery or awaiting retry
|
||||
- `delivered` — successfully delivered (HTTP 2xx from target)
|
||||
- `failed` — delivery failed; retry scheduled
|
||||
- `dead_letter` — all retries exhausted; no further attempts
|
||||
example: delivered
|
||||
|
||||
WebhookSubscription:
|
||||
type: object
|
||||
description: A registered webhook subscription (signing secret never included in responses).
|
||||
required:
|
||||
- id
|
||||
- organization_id
|
||||
- name
|
||||
- url
|
||||
- events
|
||||
- active
|
||||
- failure_count
|
||||
- created_at
|
||||
- updated_at
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Immutable system-assigned UUID for this subscription.
|
||||
readOnly: true
|
||||
example: "wh-abcd-1234-5678-ef01"
|
||||
organization_id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Organization that owns this subscription.
|
||||
readOnly: true
|
||||
example: "org-1234-5678-abcd-ef01"
|
||||
name:
|
||||
type: string
|
||||
description: Human-readable label for this subscription.
|
||||
example: "Agent lifecycle events"
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
description: HTTPS endpoint that receives webhook payloads.
|
||||
example: "https://my-app.example.com/webhooks/sentryagent"
|
||||
events:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/WebhookEventType'
|
||||
description: List of event types this subscription receives.
|
||||
minItems: 1
|
||||
example:
|
||||
- agent.created
|
||||
- agent.decommissioned
|
||||
active:
|
||||
type: boolean
|
||||
description: Whether the subscription is currently active. Set to false to pause delivery.
|
||||
example: true
|
||||
failure_count:
|
||||
type: integer
|
||||
description: Number of consecutive delivery failures since last success.
|
||||
minimum: 0
|
||||
readOnly: true
|
||||
example: 0
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-03-01T08:00:00.000Z"
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-03-28T11:30:00.000Z"
|
||||
|
||||
CreateWebhookRequest:
|
||||
type: object
|
||||
description: Request body for creating a new webhook subscription.
|
||||
required:
|
||||
- name
|
||||
- url
|
||||
- events
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: Human-readable label for this subscription.
|
||||
minLength: 1
|
||||
maxLength: 256
|
||||
example: "Agent lifecycle events"
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
description: HTTPS endpoint URL. Must be HTTPS in production.
|
||||
example: "https://my-app.example.com/webhooks/sentryagent"
|
||||
events:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/WebhookEventType'
|
||||
minItems: 1
|
||||
description: Event types to subscribe to.
|
||||
example:
|
||||
- agent.created
|
||||
- agent.decommissioned
|
||||
|
||||
UpdateWebhookRequest:
|
||||
type: object
|
||||
description: |
|
||||
Request body for partially updating a webhook subscription.
|
||||
All fields are optional; only provided fields are updated.
|
||||
minProperties: 1
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 256
|
||||
example: "Agent events (updated)"
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
example: "https://my-app.example.com/webhooks/v2"
|
||||
events:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/WebhookEventType'
|
||||
minItems: 1
|
||||
example:
|
||||
- agent.created
|
||||
- agent.updated
|
||||
- token.issued
|
||||
active:
|
||||
type: boolean
|
||||
description: Set to false to pause delivery without deleting the subscription.
|
||||
example: true
|
||||
|
||||
WebhookDelivery:
|
||||
type: object
|
||||
description: A single webhook delivery attempt record.
|
||||
required:
|
||||
- id
|
||||
- subscription_id
|
||||
- event_type
|
||||
- payload
|
||||
- status
|
||||
- attempt_count
|
||||
- created_at
|
||||
- updated_at
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
example: "del-abcd-1234-5678-ef01"
|
||||
subscription_id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "wh-abcd-1234-5678-ef01"
|
||||
event_type:
|
||||
$ref: '#/components/schemas/WebhookEventType'
|
||||
payload:
|
||||
type: object
|
||||
description: The JSON payload that was sent (or attempted) to the target URL.
|
||||
additionalProperties: true
|
||||
example:
|
||||
id: "evt-1234-5678"
|
||||
event: "agent.created"
|
||||
timestamp: "2026-04-07T09:00:00.000Z"
|
||||
organization_id: "org-1234-5678-abcd-ef01"
|
||||
data:
|
||||
agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
status:
|
||||
$ref: '#/components/schemas/WebhookDeliveryStatus'
|
||||
http_status_code:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: HTTP status code returned by the target endpoint (null if connection failed).
|
||||
example: 200
|
||||
attempt_count:
|
||||
type: integer
|
||||
description: Number of delivery attempts made so far.
|
||||
minimum: 1
|
||||
example: 1
|
||||
next_retry_at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: Scheduled time for the next retry attempt. Null if delivered or dead-lettered.
|
||||
example: null
|
||||
delivered_at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: Timestamp when the delivery was successfully confirmed.
|
||||
example: "2026-04-07T09:00:05.000Z"
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-04-07T09:00:00.000Z"
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
example: "2026-04-07T09:00:05.000Z"
|
||||
|
||||
PaginatedDeliveriesResponse:
|
||||
type: object
|
||||
description: Paginated delivery history response.
|
||||
required:
|
||||
- deliveries
|
||||
- total
|
||||
- limit
|
||||
- offset
|
||||
properties:
|
||||
deliveries:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/WebhookDelivery'
|
||||
total:
|
||||
type: integer
|
||||
example: 87
|
||||
limit:
|
||||
type: integer
|
||||
example: 20
|
||||
offset:
|
||||
type: integer
|
||||
example: 0
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: "WEBHOOK_NOT_FOUND"
|
||||
message:
|
||||
type: string
|
||||
example: "Webhook subscription with the specified ID was not found."
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Missing or invalid Bearer token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "UNAUTHORIZED"
|
||||
message: "A valid Bearer token is required to access this resource."
|
||||
|
||||
Forbidden:
|
||||
description: Valid token but insufficient scope.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "FORBIDDEN"
|
||||
message: "You do not have permission to perform this action."
|
||||
|
||||
NotFound:
|
||||
description: Webhook subscription not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "WEBHOOK_NOT_FOUND"
|
||||
message: "Webhook subscription with the specified ID was not found."
|
||||
|
||||
InternalServerError:
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
paths:
|
||||
/webhooks:
|
||||
post:
|
||||
operationId: createWebhookSubscription
|
||||
tags:
|
||||
- Webhook Subscriptions
|
||||
summary: Create a webhook subscription
|
||||
description: |
|
||||
Creates a new webhook subscription for the authenticated organization.
|
||||
A signing secret is generated automatically and returned once in the response
|
||||
under a `signingSecret` field. **Store this secret securely — it cannot be retrieved again.**
|
||||
|
||||
Requires `webhooks:write` scope.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateWebhookRequest'
|
||||
example:
|
||||
name: "Agent lifecycle events"
|
||||
url: "https://my-app.example.com/webhooks/sentryagent"
|
||||
events:
|
||||
- agent.created
|
||||
- agent.decommissioned
|
||||
responses:
|
||||
'201':
|
||||
description: Webhook subscription created successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/WebhookSubscription'
|
||||
- type: object
|
||||
properties:
|
||||
signingSecret:
|
||||
type: string
|
||||
description: |
|
||||
HMAC signing secret for verifying webhook payloads.
|
||||
Returned only once at creation time.
|
||||
example: "whsec_abcdef1234567890abcdef1234567890"
|
||||
example:
|
||||
id: "wh-abcd-1234-5678-ef01"
|
||||
organization_id: "org-1234-5678-abcd-ef01"
|
||||
name: "Agent lifecycle events"
|
||||
url: "https://my-app.example.com/webhooks/sentryagent"
|
||||
events:
|
||||
- agent.created
|
||||
- agent.decommissioned
|
||||
active: true
|
||||
failure_count: 0
|
||||
created_at: "2026-04-07T09:00:00.000Z"
|
||||
updated_at: "2026-04-07T09:00:00.000Z"
|
||||
signingSecret: "whsec_abcdef1234567890abcdef1234567890"
|
||||
'400':
|
||||
description: Validation error in request body.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Request validation failed."
|
||||
details:
|
||||
field: "url"
|
||||
reason: "Must be a valid HTTPS URL."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
get:
|
||||
operationId: listWebhookSubscriptions
|
||||
tags:
|
||||
- Webhook Subscriptions
|
||||
summary: List webhook subscriptions
|
||||
description: |
|
||||
Returns all webhook subscriptions for the authenticated organization.
|
||||
Requires `webhooks:read` scope.
|
||||
responses:
|
||||
'200':
|
||||
description: List of webhook subscriptions returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/WebhookSubscription'
|
||||
example:
|
||||
- id: "wh-abcd-1234-5678-ef01"
|
||||
organization_id: "org-1234-5678-abcd-ef01"
|
||||
name: "Agent lifecycle events"
|
||||
url: "https://my-app.example.com/webhooks/sentryagent"
|
||||
events:
|
||||
- agent.created
|
||||
- agent.decommissioned
|
||||
active: true
|
||||
failure_count: 0
|
||||
created_at: "2026-03-01T08:00:00.000Z"
|
||||
updated_at: "2026-03-01T08:00:00.000Z"
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/webhooks/{id}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: UUID of the webhook subscription.
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "wh-abcd-1234-5678-ef01"
|
||||
|
||||
get:
|
||||
operationId: getWebhookSubscription
|
||||
tags:
|
||||
- Webhook Subscriptions
|
||||
summary: Get a webhook subscription by ID
|
||||
description: |
|
||||
Returns a single webhook subscription record.
|
||||
Requires `webhooks:read` scope.
|
||||
responses:
|
||||
'200':
|
||||
description: Webhook subscription returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/WebhookSubscription'
|
||||
example:
|
||||
id: "wh-abcd-1234-5678-ef01"
|
||||
organization_id: "org-1234-5678-abcd-ef01"
|
||||
name: "Agent lifecycle events"
|
||||
url: "https://my-app.example.com/webhooks/sentryagent"
|
||||
events:
|
||||
- agent.created
|
||||
- agent.decommissioned
|
||||
active: true
|
||||
failure_count: 0
|
||||
created_at: "2026-03-01T08:00:00.000Z"
|
||||
updated_at: "2026-03-28T11:30:00.000Z"
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
patch:
|
||||
operationId: updateWebhookSubscription
|
||||
tags:
|
||||
- Webhook Subscriptions
|
||||
summary: Update a webhook subscription
|
||||
description: |
|
||||
Partially updates a webhook subscription. Only provided fields are updated.
|
||||
Set `active: false` to pause delivery without deleting the subscription.
|
||||
Requires `webhooks:write` scope.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UpdateWebhookRequest'
|
||||
example:
|
||||
active: false
|
||||
responses:
|
||||
'200':
|
||||
description: Webhook subscription updated successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/WebhookSubscription'
|
||||
example:
|
||||
id: "wh-abcd-1234-5678-ef01"
|
||||
organization_id: "org-1234-5678-abcd-ef01"
|
||||
name: "Agent lifecycle events"
|
||||
url: "https://my-app.example.com/webhooks/sentryagent"
|
||||
events:
|
||||
- agent.created
|
||||
- agent.decommissioned
|
||||
active: false
|
||||
failure_count: 0
|
||||
created_at: "2026-03-01T08:00:00.000Z"
|
||||
updated_at: "2026-04-07T09:00:00.000Z"
|
||||
'400':
|
||||
description: Validation error in request body.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Request validation failed."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
delete:
|
||||
operationId: deleteWebhookSubscription
|
||||
tags:
|
||||
- Webhook Subscriptions
|
||||
summary: Delete a webhook subscription
|
||||
description: |
|
||||
Permanently deletes a webhook subscription and stops all future deliveries.
|
||||
Any pending deliveries in the queue are cancelled.
|
||||
Requires `webhooks:write` scope.
|
||||
responses:
|
||||
'204':
|
||||
description: Webhook subscription deleted successfully. No response body.
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/webhooks/{id}/deliveries:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: UUID of the webhook subscription.
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "wh-abcd-1234-5678-ef01"
|
||||
|
||||
get:
|
||||
operationId: listWebhookDeliveries
|
||||
tags:
|
||||
- Webhook Deliveries
|
||||
summary: List delivery history for a subscription
|
||||
description: |
|
||||
Returns the delivery history for a webhook subscription,
|
||||
ordered by `created_at` descending (most recent first).
|
||||
|
||||
Use this endpoint to diagnose delivery failures and inspect
|
||||
payload content for historical events.
|
||||
|
||||
Requires `webhooks:read` scope.
|
||||
parameters:
|
||||
- name: limit
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
default: 20
|
||||
example: 20
|
||||
- name: offset
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 0
|
||||
default: 0
|
||||
example: 0
|
||||
- name: status
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
$ref: '#/components/schemas/WebhookDeliveryStatus'
|
||||
description: Filter deliveries by status.
|
||||
responses:
|
||||
'200':
|
||||
description: Delivery history returned successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PaginatedDeliveriesResponse'
|
||||
example:
|
||||
deliveries:
|
||||
- id: "del-abcd-1234-5678-ef01"
|
||||
subscription_id: "wh-abcd-1234-5678-ef01"
|
||||
event_type: "agent.created"
|
||||
payload:
|
||||
id: "evt-1234-5678"
|
||||
event: "agent.created"
|
||||
timestamp: "2026-04-07T09:00:00.000Z"
|
||||
organization_id: "org-1234-5678-abcd-ef01"
|
||||
data:
|
||||
agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
status: "delivered"
|
||||
http_status_code: 200
|
||||
attempt_count: 1
|
||||
next_retry_at: null
|
||||
delivered_at: "2026-04-07T09:00:05.000Z"
|
||||
created_at: "2026-04-07T09:00:00.000Z"
|
||||
updated_at: "2026-04-07T09:00:05.000Z"
|
||||
total: 87
|
||||
limit: 20
|
||||
offset: 0
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
Reference in New Issue
Block a user