Awesome-omni-skill api-guide
Guide for implementing REST and GraphQL APIs (create, get, search, update, delete, purge, scope prefix patterns, admin_ prefix, SearchScope, BaseFilterAdapter, @api_function, Click CLI)
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/api-guide" ~/.claude/skills/diegosouzapw-awesome-omni-skill-api-guide && rm -rf "$T"
skills/development/api-guide/SKILL.mdAPI 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:
- Handlerssrc/ai/backend/manager/api/{domain}/handler.py
- Filterssrc/ai/backend/manager/api/{domain}/adapter.py
- Base adaptersrc/ai/backend/manager/api/adapter.py
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
with Actionwait_for_complete() - 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:
- Handler calling processorapi/fair_share/handler.py:144
- Processor implementationservices/storage_namespace/processors.py:29-56
- ActionProcessor base classservices/processors.py
Scope Pattern
Scope defines access boundaries.
Repository Scope:
src/ai/backend/manager/repositories/{domain}/types.py- Example:
repositories/fair_share/types.py
,DomainFairShareSearchScopeProjectFairShareSearchScope
Pattern:
- Frozen dataclass with scope params
converts to query conditionsto_conditions()
See complete examples:
- Scoped handler implementationsapi/fair_share/handler.py
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) # ✅ No scope needed # domain_user(scope: DomainScope, id: ID) # ❌ Unnecessary -
update/delete/purge - ID uniquely identifies item
update_user(id: ID, data: ...) # ✅ No scope delete_user(id: ID) # ✅ No scope -
create - Scope info in data
create_user(data: CreateUserData) # data contains domain_name
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:
- BaseFilterAdapterapi/adapter.py
- Domain adaptersapi/fair_share/adapter.py
Pattern:
→to_conditions(filters)list[QueryCondition]
→to_orders(order_by)list[QueryOrder]- Adapter →
→ RepositoryBatchQuerier
See complete examples:
- Filter adaptersapi/fair_share/adapter.py
- Handler implementations with BatchQuerierapi/fair_share/handler.py
Admin_ Prefix Pattern
Admin operations require superadmin check before processing.
Pattern:
at handler start_check_superadmin(request)- Raise
if not superadminInsufficientPermission
See complete examples:
- Admin handler implementationsapi/rbac/handler.py
Pagination
REST uses offset-based pagination.
class PaginationQuery(BaseModel): offset: int = 0 limit: int = 20 # Response 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:
- Resolverssrc/ai/backend/manager/api/gql/{domain}/resolver/
- Input typessrc/ai/backend/manager/api/gql/types.py
- Utilitiessrc/ai/backend/manager/api/gql/utils.py
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
suffix (DomainGQL, DomainScopeGQL, DomainFilterGQL)GQL - 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:
# 1. TYPE_CHECKING: for static analysis (mypy) if TYPE_CHECKING: from ai.backend.manager.api.gql.domain_v2.types.node import DomainV2GQL # 2. Return type: Annotated with strawberry.lazy() for runtime resolution # 3. Function body: runtime import + DataLoader 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("...")]: # ❌ lazy cannot resolve union
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:
- Cross-entity reference with DataLoaderapi/gql/fair_share/types/domain.py
- DomainV2GQL with fair_shares/usage_bucketsapi/gql/domain_v2/types/node.py
- Scope and Filter patternsapi/gql/fair_share/types/*.py
Scope Pattern
Input Types:
api/gql/types.py
,ResourceGroupDomainScope
,ResourceGroupProjectScopeResourceGroupUserScope
Pattern:
- Strawberry
maps to repository SearchScope@input - GraphQL input → Repository scope type conversion
See complete examples:
- Scope input typesapi/gql/types.py
- Resolver implementationsapi/gql/fair_share/resolver/domain.py
Admin Check
Pattern:
at resolver startcheck_admin_only(info)- Raise
if not superadminInsufficientPermission
See complete examples:
-api/gql/utils.py
utilitycheck_admin_only()
- Admin resolver implementationsapi/gql/*/resolver/*.py
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 | | |
| Naming | | |
| 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
decorator@api_function - Map to REST endpoint
- Use
-
✅ CLI Command (
)client/cli/admin/{domain}.py- Click command
- Calls SDK function
Integration flow:
CLI → SDK → REST API → Processor → Service → Repository
See examples:
- SDKsrc/ai/backend/client/func/admin.py
- CLIsrc/ai/backend/client/cli/admin/user.py
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
decorator@api_function
-
✅ CLI
- Click command
- Integrate with SDK
-
✅ Tests
- Repository (real DB)
- Service (mock repo)
- Handler (mock processors)
- CLI (mock HTTP)
Related Documentation
- Service Layer:
- Actions, Processors/service-guide - Repository Layer:
- Data access/repository-guide - Testing:
- TDD workflow/tdd-guide - API README:
src/ai/backend/manager/api/README.md
Examples
REST API:
src/ai/backend/manager/api/fair_share/handler.pysrc/ai/backend/manager/api/rbac/handler.py
GraphQL:
src/ai/backend/manager/api/gql/fair_share/resolver/domain.pysrc/ai/backend/manager/api/gql/types.py
Client SDK/CLI:
src/ai/backend/client/func/admin.pysrc/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:
(domain_create_user){scope}_operation - Admin:
(admin_create_domain)admin_operation
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