Files
sentryagent-idp/docs/openapi/organizations.yaml
SentryAgent.ai Developer 7441c9f298 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>
2026-04-07 04:52:47 +00:00

708 lines
21 KiB
YAML

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'