Skip to content
Back to skills

Api Guide

ASecurity

Guide for implementing REST and GraphQL APIs (create, get, search, update, delete, purge, scope prefix patterns, admin_ prefix, SearchScope, BaseFilterAdapter, @api_function, Click CLI)

  • 57 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added May 31, 2026
businesspythonnodetestingapibackenddocumentation

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Scanned May 31, 2026

npx -y skills add diegosouzapw/awesome-omni-skill --skill api-guide --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Api Guide?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Api Guide
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/diegosouzapw-api-guide/badge)](https://www.skillsdirectory.com/skills/diegosouzapw-api-guide)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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:

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:**
```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:**
```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
   ```python
   domain_search_users(scope: DomainScope, filter: ...)
   ```

2. **batch operations** - Process multiple items within scope
   ```python
   domain_batch_update_users(scope: DomainScope, ids: list[ID], ...)
   ```

**Scope parameter is NOT needed for:**

1. **get** - ID uniquely identifies item
   ```python
   user(id: ID)  # ✅ No scope needed
   # domain_user(scope: DomainScope, id: ID)  # ❌ Unnecessary
   ```

2. **update/delete/purge** - ID uniquely identifies item
   ```python
   update_user(id: ID, data: ...)  # ✅ No scope
   delete_user(id: ID)  # ✅ No scope
   ```

3. **create** - Scope info in data
   ```python
   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.

```python
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:**
```python
# 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[]`:
```python
) -> 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).

```python
@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:**

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`)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.