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 `. 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'