| name | api-guide |
| description | Guide for implementing REST and GraphQL APIs (create, get, search, update, delete, purge, scope prefix patterns, admin_ prefix, SearchScope, BaseFilterAdapter, @api_function, Click CLI) |
| version | 1.0.0 |
| dependencies | ["service-guide","repository-guide"] |
| tags | ["rest-api","graphql","client-sdk","cli","api-patterns"] |
API Implementation Guide
Guide for implementing REST and GraphQL APIs with standard operations and scope patterns.
Standard Operations
APIs implement 6 standard operations:
- create - Create new entity
- get - Retrieve single entity
- search - Query with filters and pagination
- update - Update entity
- delete - Delete entity (soft)
- purge - Permanently remove entity (hard)
Batch operations: batch_update, batch_delete, batch_purge
Scope Prefix Rules
API layer only (Service/Repository layers don't use prefix)
Scoped Operations
Operations within a scope use {scope}_ prefix:
REST:
POST /domains/{domain}/users → domain_create_user
GET /domains/{domain}/users/{id} → domain_user
GET /domains/{domain}/users → domain_search_users
PATCH /domains/{domain}/users/{id} → domain_update_user
DELETE /domains/{domain}/users/{id} → domain_delete_user
GraphQL:
mutation domainCreateUser(scope: DomainScope, input: ...)
query domainUser(scope: DomainScope, id: ID)
query domainSearchUsers(scope: DomainScope, filter: ...)
Admin Operations (No Scope)
Operations without scope use admin_ prefix (superadmin required):
REST:
POST /admin/domains → admin_create_domain
GET /admin/domains/{id} → admin_domain
GET /admin/domains → admin_search_domains
GraphQL:
mutation adminCreateDomain(input: ...)
query adminDomain(id: ID)
query adminSearchDomains(filter: ...)
REST API Patterns
Architecture
REST Handler → Processor → Service → Repository
Key Files:
src/ai/backend/manager/api/{domain}/handler.py - Handlers
src/ai/backend/manager/api/{domain}/adapter.py - Filters
src/ai/backend/manager/api/adapter.py - Base adapter
Processor-Based Service Invocation
Core principle:
- REST/GraphQL handlers MUST call service methods through Processors
- Never call service methods directly from handlers
- Processors wrap service methods as Actions and provide cross-cutting concerns (hooks, metrics, monitoring)
Pattern:
- Handler creates Action with operation parameters
- Handler calls processor's
wait_for_complete() with Action
- Processor invokes corresponding service method and returns ActionResult
ActionProcessor responsibilities:
- Action-based service method invocation
- Hook execution (pre/post operation)
- Metrics collection and monitoring
- Error handling and logging
See complete examples:
api/fair_share/handler.py:144 - Handler calling processor
services/storage_namespace/processors.py:29-56 - Processor implementation
services/processors.py - ActionProcessor base class
Scope Pattern
Scope defines access boundaries.
Repository Scope:
src/ai/backend/manager/repositories/{domain}/types.py
- Example:
repositories/fair_share/types.py
DomainFairShareSearchScope, ProjectFairShareSearchScope
Pattern:
- Frozen dataclass with scope params
to_conditions() converts to query conditions
See complete examples:
api/fair_share/handler.py - Scoped handler implementations
Scope Parameter Usage
Scope parameter is needed for:
-
search - Filter multiple items within scope
domain_search_users(scope: DomainScope, filter: ...)
-
batch operations - Process multiple items within scope
domain_batch_update_users(scope: DomainScope, ids: list[ID], ...)
Scope parameter is NOT needed for:
-
get - ID uniquely identifies item
user(id: ID)
-
update/delete/purge - ID uniquely identifies item
update_user(id: ID, data: ...)
delete_user(id: ID)
-
create - Scope info in data
create_user(data: CreateUserData)
Note: Scope prefix in API name (domain_search_users) is different from scope parameter (scope: DomainScope).
Filter Pattern
Filters convert API params to QueryCondition.
Adapter:
api/adapter.py - BaseFilterAdapter
api/fair_share/adapter.py - Domain adapters
Pattern:
to_conditions(filters) → list[QueryCondition]
to_orders(order_by) → list[QueryOrder]
- Adapter →
BatchQuerier → Repository
See complete examples:
api/fair_share/adapter.py - Filter adapters
api/fair_share/handler.py - Handler implementations with BatchQuerier
Admin_ Prefix Pattern
Admin operations require superadmin check before processing.
Pattern:
_check_superadmin(request) at handler start
- Raise
InsufficientPermission if not superadmin
See complete examples:
api/rbac/handler.py - Admin handler implementations
Pagination
REST uses offset-based pagination.
class PaginationQuery(BaseModel):
offset: int = 0
limit: int = 20
class SearchResult(BaseModel):
items: list[Item]
total_count: int
offset: int
limit: int
Client calculates: has_next = offset + limit < total_count
GraphQL Patterns
Architecture
GraphQL Resolver → check_admin_only (if admin) → Processor → Service → Repository
Key Files:
src/ai/backend/manager/api/gql/{domain}/resolver/ - Resolvers
src/ai/backend/manager/api/gql/types.py - Input types
src/ai/backend/manager/api/gql/utils.py - Utilities
Type System Rules
Strawberry Runtime Evaluation:
- Strawberry types are evaluated at runtime
- NEVER use TYPE_CHECKING for Strawberry types (Connection, Filter, OrderBy, Input, Type)
- ALWAYS import Strawberry types directly at module level
- Only use TYPE_CHECKING for data layer types not used by Strawberry
- If lazy import needed: use strawberry.lazy() or string-based forward references
Naming Convention:
- All GraphQL types MUST have
GQL suffix (DomainGQL, DomainScopeGQL, DomainFilterGQL)
- Distinguishes GraphQL types from data layer types
- Applies to: @strawberry.type, @strawberry.input, @strawberry.enum classes
Scope vs Filter:
- Scope: Required context parameters (resource_group, domain_name, project_id)
- Filter: Optional filtering conditions (name contains, status equals, created after)
- NEVER put optional fields in Scope - use Filter instead
- Scope fields must all be required (no default values, no Optional types)
Cross-Entity Reference Resolvers
When a GQL node references another entity node, use strawberry.lazy() to avoid circular imports. Strawberry requires runtime type resolution, so TYPE_CHECKING imports alone are insufficient.
Pattern:
if TYPE_CHECKING:
from ai.backend.manager.api.gql.domain_v2.types.node import DomainV2GQL
async def domain(self, info: Info[StrawberryGQLContext]) -> Annotated[
DomainV2GQL,
strawberry.lazy("ai.backend.manager.api.gql.domain_v2.types.node"),
]:
from ai.backend.manager.api.gql.domain_v2.types.node import DomainV2GQL
data = await info.context.data_loaders.domain_loader.load(self.domain_name)
return DomainV2GQL.from_data(data)
Optional return type: | None must be outside Annotated[]:
) -> Annotated[DomainV2GQL, strawberry.lazy("...")] | None:
) -> Annotated[DomainV2GQL | None, strawberry.lazy("...")]:
DataLoaders (info.context.data_loaders): Use DataLoaders instead of individual fetch functions to prevent N+1 queries. See api/gql/data_loader/data_loaders.py for available loaders.
See examples:
api/gql/fair_share/types/domain.py - Cross-entity reference with DataLoader
api/gql/domain_v2/types/node.py - DomainV2GQL with fair_shares/usage_buckets
api/gql/fair_share/types/*.py - Scope and Filter patterns
Scope Pattern
Input Types:
api/gql/types.py
ResourceGroupDomainScope, ResourceGroupProjectScope, ResourceGroupUserScope
Pattern:
- Strawberry
@input maps to repository SearchScope
- GraphQL input → Repository scope type conversion
See complete examples:
api/gql/types.py - Scope input types
api/gql/fair_share/resolver/domain.py - Resolver implementations
Admin Check
Pattern:
check_admin_only(info) at resolver start
- Raise
InsufficientPermission if not superadmin
See complete examples:
api/gql/utils.py - check_admin_only() utility
api/gql/*/resolver/*.py - Admin resolver implementations
Pagination
GraphQL supports cursor-based (Relay spec).
@strawberry.type
class PageInfo:
has_next_page: bool
has_previous_page: bool
start_cursor: str | None
end_cursor: str | None
@strawberry.type
class UserConnection:
edges: list[UserEdge]
page_info: PageInfo
total_count: int
See: api/gql/ for cursor pagination examples
REST vs GraphQL
| Aspect | REST | GraphQL |
|---|
| Scope | PathParam/QueryParam | Strawberry input |
| Admin Check | _check_superadmin(request) | check_admin_only(info) |
| Naming | domain_create_user | domainCreateUser |
| Pagination | Offset | Offset or Cursor |
| Response | Pydantic | Strawberry type |
Client SDK + CLI Integration
When implementing REST API, also implement:
-
✅ SDK Function (client/func/{domain}.py)
- Use
@api_function decorator
- Map to REST endpoint
-
✅ CLI Command (client/cli/admin/{domain}.py)
- Click command
- Calls SDK function
Integration flow:
CLI → SDK → REST API → Processor → Service → Repository
See examples:
src/ai/backend/client/func/admin.py - SDK
src/ai/backend/client/cli/admin/user.py - CLI
Testing
See: /tdd-guide skill and tests/CLAUDE.md for complete testing strategies.
Test hierarchy:
Repository Tests → Real DB (with_tables)
Service Tests → Mock repositories
API Handler Tests → Mock processors
CLI Tests → Mock HTTP
Implementation Checklist
When implementing new API:
-
✅ Repository (/repository-guide)
- Implement standard operations
- Define SearchScope
-
✅ Service (/service-guide)
- Define Actions/ActionResults
- Implement service methods
- Create processors
-
✅ REST API
- Handler with scope prefix
- Admin check if needed
- Filter adapter
-
✅ GraphQL (optional)
- Input types
- Resolver with scope prefix
- Admin check if needed
-
✅ Client SDK
- Add SDK function
@api_function decorator
-
✅ CLI
- Click command
- Integrate with SDK
-
✅ Tests
- Repository (real DB)
- Service (mock repo)
- Handler (mock processors)
- CLI (mock HTTP)
Related Documentation
- Service Layer:
/service-guide - Actions, Processors
- Repository Layer:
/repository-guide - Data access
- Testing:
/tdd-guide - TDD workflow
- API README:
src/ai/backend/manager/api/README.md
Examples
REST API:
src/ai/backend/manager/api/fair_share/handler.py
src/ai/backend/manager/api/rbac/handler.py
GraphQL:
src/ai/backend/manager/api/gql/fair_share/resolver/domain.py
src/ai/backend/manager/api/gql/types.py
Client SDK/CLI:
src/ai/backend/client/func/admin.py
src/ai/backend/client/cli/admin/user.py
Summary
Standard operations:
- create, get, search, update, delete, purge
- batch_update, batch_delete, batch_purge
Scope prefix (API only):
- Scoped:
{scope}_operation (domain_create_user)
- Admin:
admin_operation (admin_create_domain)
Key patterns:
- Scope → SearchScope → QueryCondition
- Filter → Adapter → QueryCondition
- Admin → check permission first
Integration:
- REST API + SDK + CLI (unified stack)
- GraphQL (separate, optional)
Next steps:
- Implement repository (
/repository-guide)
- Implement service (
/service-guide)
- Implement API handlers
- Add SDK + CLI
- Write tests (
/tdd-guide)