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)

install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
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"
manifest: skills/development/api-guide/SKILL.md
source content

API Implementation Guide

Guide for implementing REST and GraphQL APIs with standard operations and scope patterns.

Standard Operations

APIs implement 6 standard operations:

  1. create - Create new entity
  2. get - Retrieve single entity
  3. search - Query with filters and pagination
  4. update - Update entity
  5. delete - Delete entity (soft)
  6. 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:

  1. Handler creates Action with operation parameters
  2. Handler calls processor's
    wait_for_complete()
    with Action
  3. 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:

  1. search - Filter multiple items within scope

    domain_search_users(scope: DomainScope, filter: ...)
    
  2. batch operations - Process multiple items within scope

    domain_batch_update_users(scope: DomainScope, ids: list[ID], ...)
    

Scope parameter is NOT needed for:

  1. get - ID uniquely identifies item

    user(id: ID)  # ✅ No scope needed
    # domain_user(scope: DomainScope, id: ID)  # ❌ Unnecessary
    
  2. update/delete/purge - ID uniquely identifies item

    update_user(id: ID, data: ...)  # ✅ No scope
    delete_user(id: ID)  # ✅ No scope
    
  3. 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:

  • 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

# 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:

  • 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:

# 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:

  • 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

AspectRESTGraphQL
ScopePathParam/QueryParamStrawberry input
Admin Check
_check_superadmin(request)
check_admin_only(info)
Naming
domain_create_user
domainCreateUser
PaginationOffsetOffset or Cursor
ResponsePydanticStrawberry type

Client SDK + CLI Integration

When implementing REST API, also implement:

  1. SDK Function (

    client/func/{domain}.py
    )

    • Use
      @api_function
      decorator
    • Map to REST endpoint
  2. 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:

  1. Repository (

    /repository-guide
    )

    • Implement standard operations
    • Define SearchScope
  2. Service (

    /service-guide
    )

    • Define Actions/ActionResults
    • Implement service methods
    • Create processors
  3. REST API

    • Handler with scope prefix
    • Admin check if needed
    • Filter adapter
  4. GraphQL (optional)

    • Input types
    • Resolver with scope prefix
    • Admin check if needed
  5. Client SDK

    • Add SDK function
    • @api_function
      decorator
  6. CLI

    • Click command
    • Integrate with SDK
  7. 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:

  1. Implement repository (
    /repository-guide
    )
  2. Implement service (
    /service-guide
    )
  3. Implement API handlers
  4. Add SDK + CLI
  5. Write tests (
    /tdd-guide
    )