AgentSkillsCN

api-standards

RawDrive的API规范。适用于构建API端点、设计响应格式、实现分页,或处理HTTP方法时使用。

SKILL.md
--- frontmatter
name: api-standards
aliases: [api, rest, endpoints, http, responses, pagination]
description: API conventions for RawDrive. Use when building API endpoints, designing response formats, implementing pagination, or handling HTTP methods.

API Standards

URL Structure

code
/api/v1/{resource}
/api/v1/workspaces/{workspace_id}/{resource}
/api/v1/workspaces/{workspace_id}/{resource}/{id}
/api/v1/workspaces/{workspace_id}/{resource}/{id}/{sub-resource}

Examples

code
GET    /api/v1/workspaces/{id}/galleries
POST   /api/v1/workspaces/{id}/galleries
GET    /api/v1/workspaces/{id}/galleries/{id}
PATCH  /api/v1/workspaces/{id}/galleries/{id}
DELETE /api/v1/workspaces/{id}/galleries/{id}
GET    /api/v1/workspaces/{id}/galleries/{id}/assets

Naming Conventions

PatternExample
Resourcesgalleries, assets, users (plural)
Sub-resourcesgalleries/{id}/assets
ActionsPOST /galleries/{id}/publish
Kebab-caseface-groups, upload-sessions

HTTP Methods

MethodUse CaseIdempotent
GETRetrieve resource(s)Yes
POSTCreate resource or actionNo
PATCHPartial updateYes
PUTFull replacementYes
DELETERemove resourceYes

Response Format

Success (Single Resource)

json
{
  "data": {
    "id": "uuid",
    "name": "Wedding Gallery",
    "created_at": "2024-01-15T10:30:00Z"
  }
}

Success (Collection)

json
{
  "data": [
    { "id": "uuid1", "name": "Gallery 1" },
    { "id": "uuid2", "name": "Gallery 2" }
  ],
  "pagination": {
    "total": 42,
    "page": 1,
    "limit": 20,
    "pages": 3
  }
}

Error Response

json
{
  "error": "ValidationError",
  "message": "Please correct the errors below",
  "details": [
    { "field": "name", "message": "Name is required" },
    { "field": "email", "message": "Invalid email format" }
  ],
  "request_id": "req_abc123"
}

Status Codes

CodeUse Case
200Success (GET, PATCH, DELETE)
201Created (POST)
204No Content (DELETE with no body)
400Bad Request (validation error)
401Unauthorized (no/invalid token)
403Forbidden (no permission)
404Not Found
409Conflict (duplicate, state conflict)
422Unprocessable Entity (business logic)
429Too Many Requests (rate limited)
500Internal Server Error

Pagination

Offset-Based (Simple)

code
GET /galleries?page=2&limit=20
json
{
  "data": [...],
  "pagination": {
    "total": 100,
    "page": 2,
    "limit": 20,
    "pages": 5
  }
}

Cursor-Based (Large Datasets)

code
GET /assets?cursor=eyJpZCI6MTIzfQ&limit=50
json
{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6MTczfQ",
    "has_more": true
  }
}

Implementation

python
# FastAPI pagination
from fastapi import Query

@router.get("/galleries")
async def list_galleries(
    workspace_id: UUID,
    page: int = Query(1, ge=1),
    limit: int = Query(20, ge=1, le=100),
):
    offset = (page - 1) * limit
    galleries = await repo.list(workspace_id, offset=offset, limit=limit)
    total = await repo.count(workspace_id)

    return {
        "data": galleries,
        "pagination": {
            "total": total,
            "page": page,
            "limit": limit,
            "pages": (total + limit - 1) // limit,
        }
    }

Filtering & Sorting

Query Parameters

code
GET /assets?status=available&type=photo&sort=-created_at&limit=50
ParameterFormatExample
Filterfield=valuestatus=available
Multi-valuefield=a,b,ctype=photo,video
Sort (asc)sort=fieldsort=name
Sort (desc)sort=-fieldsort=-created_at
Searchq=termq=wedding

Implementation

python
@router.get("/assets")
async def list_assets(
    workspace_id: UUID,
    status: str | None = None,
    type: str | None = None,
    sort: str = "-created_at",
    q: str | None = None,
    page: int = Query(1, ge=1),
    limit: int = Query(20, ge=1, le=100),
):
    filters = {}
    if status:
        filters["status"] = status
    if type:
        filters["type"] = type.split(",")

    # Parse sort
    sort_desc = sort.startswith("-")
    sort_field = sort.lstrip("-")

    return await repo.list(
        workspace_id,
        filters=filters,
        sort_by=sort_field,
        sort_desc=sort_desc,
        search=q,
        offset=(page - 1) * limit,
        limit=limit,
    )

Request Validation

python
from pydantic import BaseModel, Field, field_validator

class CreateGalleryRequest(BaseModel):
    name: str = Field(..., min_length=1, max_length=200)
    description: str | None = Field(None, max_length=2000)
    is_public: bool = False

    @field_validator("name")
    @classmethod
    def name_not_empty(cls, v: str) -> str:
        if not v.strip():
            raise ValueError("Name cannot be blank")
        return v.strip()

class UpdateGalleryRequest(BaseModel):
    name: str | None = Field(None, min_length=1, max_length=200)
    description: str | None = Field(None, max_length=2000)
    is_public: bool | None = None

Authentication

Bearer Token

code
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Workspace Context

python
from fastapi import Depends, HTTPException
from app.auth import get_current_user

@router.get("/workspaces/{workspace_id}/galleries")
async def list_galleries(
    workspace_id: UUID,
    user: User = Depends(get_current_user),
):
    # Verify user has access to workspace
    if not await user.has_workspace_access(workspace_id):
        raise HTTPException(403, "Access denied")

    return await gallery_service.list(workspace_id)

Rate Limiting

EndpointLimit
General API100/minute
Auth endpoints5/15 minutes
Uploads1000/hour per workspace
AI operations30/minute per workspace

Response Headers

code
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200

Versioning

  • Current version: v1
  • Version in URL: /api/v1/...
  • Breaking changes require new version
  • 6 months deprecation notice

Response Header

code
X-API-Version: v1
X-API-Deprecated: true  # If using deprecated version

Common Patterns

Bulk Operations

code
POST /galleries/bulk-delete
{
  "ids": ["uuid1", "uuid2", "uuid3"]
}

Actions on Resources

code
POST /galleries/{id}/publish
POST /galleries/{id}/duplicate
POST /uploads/{id}/commit
POST /uploads/{id}/abort

Nested Resources

code
# List assets in gallery
GET /galleries/{id}/assets

# Add asset to gallery
POST /galleries/{id}/assets
{ "asset_id": "uuid" }

# Remove asset from gallery
DELETE /galleries/{id}/assets/{asset_id}

OpenAPI Documentation

python
from fastapi import FastAPI

app = FastAPI(
    title="RawDrive API",
    description="Professional photography management platform",
    version="1.0.0",
    docs_url="/api/docs",
    redoc_url="/api/redoc",
)

Endpoint Documentation

python
@router.get(
    "/galleries/{gallery_id}",
    response_model=GalleryResponse,
    summary="Get gallery by ID",
    description="Retrieves a single gallery with its metadata.",
    responses={
        404: {"description": "Gallery not found"},
        403: {"description": "Access denied"},
    },
)
async def get_gallery(gallery_id: UUID) -> GalleryResponse:
    ...