Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
40 changes: 21 additions & 19 deletions .claude/skills/update-api-docs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,40 +25,42 @@ The API documentation is generated from an OpenAPI spec using `docusaurus-plugin

## Steps

### 1. Download the OpenAPI spec from production
### 1. Run the update script

The wrapper script at `docs/scripts/update-api-docs.sh` downloads the OpenAPI
spec, replaces the local file, and regenerates the docs in one step.

```bash
curl -s "https://cloud.agenta.ai/api/openapi.json" -o docs/docs/reference/openapi.json
```
cd docs

**Important:** The file should be saved in **minified format** (single line, no pretty-printing) to match the existing format in the repository. The curl command above preserves the original format from the server.
# Default — fetch from production (live cloud API)
pnpm update-api-docs

### 2. Install dependencies (if needed)
# Explicit live cloud API
pnpm update-api-docs:live

If this is a fresh clone or dependencies haven't been installed:
# From a locally running API (http://localhost/api/openapi.json)
pnpm update-api-docs:local

```bash
cd docs
npm install
# From an explicit local file
pnpm update-api-docs:file /path/to/openapi.json
```

### 3. Clean existing generated API docs
The script writes the spec to `docs/docs/reference/openapi.json` and then
runs `npm run clean-api-docs -- agenta` followed by
`npm run gen-api-docs -- agenta`. The `agenta` argument refers to the OpenAPI
config ID defined in `docusaurus.config.ts`.

```bash
cd docs
npm run clean-api-docs -- agenta
```

The `agenta` argument refers to the OpenAPI config ID defined in `docusaurus.config.ts`.
### 2. Install dependencies (if needed)

### 4. Regenerate API docs
If this is a fresh clone or dependencies haven't been installed:

```bash
cd docs
npm run gen-api-docs -- agenta
npm install
```

This will generate:
This generates:
- Individual `.api.mdx` files for each endpoint
- `.tag.mdx` files for API categories
- `sidebar.ts` for navigation
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- If you make changes to the frontend, make sure to run `pnpm lint-fix` within the web folder
- If you make changes to the API or SDK, make sure to run `ruff format` and `ruff check --fix` within the SDK or API folder (run from the repo root: `ruff format` then `ruff check`; fix all errors before committing)
- If you update Ant Design tokens, run `pnpm generate:tailwind-tokens` in the web folder and commit the generated file
- The Fern-generated `@agenta/api-client` ships as a compiled `dist/` (entry: `./dist/index.js`). `pnpm install` runs the package's `prepare` script which builds `dist/` automatically — so a fresh checkout works out of the box. **If you regenerate the client (`bash ./clients/scripts/generate.sh --language typescript`) or edit `web/packages/agenta-api-client/src/`, run `pnpm install` again or `pnpm --filter @agenta/api-client build` so consumers (`@agenta/sdk`, `@agenta/entities`, `web/oss`, `web/ee`) see the update. The `.js` extensions in Fern's relative imports are intentional NodeNext-style emission and resolve only via the compiled `dist/`.**
- The Fern-generated `@agentaai/api-client` ships as a compiled `dist/` (entry: `./dist/index.js`). `pnpm install` runs the package's `prepare` script which builds `dist/` automatically — so a fresh checkout works out of the box. **If you regenerate the client (`bash ./clients/scripts/generate.sh --language typescript`) or edit `web/packages/agenta-api-client/src/`, run `pnpm install` again or `pnpm --filter @agentaai/api-client build` so consumers (`@agenta/sdk`, `@agenta/entities`, `web/oss`, `web/ee`) see the update. The `.js` extensions in Fern's relative imports are intentional NodeNext-style emission and resolve only via the compiled `dist/`.**

## Environment Config Conventions
- For API configuration, add new environment variables to `api/oss/src/utils/env.py` and consume them via the shared `env` object.
Expand Down
7 changes: 0 additions & 7 deletions api/ee/src/apis/fastapi/billing/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,50 +165,43 @@ def __init__(
"/stripe/portals/",
self.create_portal_admin_route,
methods=["POST"],
operation_id="admin_create_portal",
)

self.admin_router.add_api_route(
"/stripe/checkouts/",
self.create_checkout_admin_route,
methods=["POST"],
operation_id="admin_create_checkout",
)

self.admin_router.add_api_route(
"/plans/switch",
self.switch_plans_admin_route,
methods=["POST"],
operation_id="admin_switch_plans",
)

self.admin_router.add_api_route(
"/subscription/cancel",
self.cancel_subscription_admin_route,
methods=["POST"],
operation_id="admin_cancel_subscription",
)

# DOESN'T REQUIRE 'organization_id'
self.admin_router.add_api_route(
"/usage/report",
self.report_usage,
methods=["POST"],
operation_id="admin_report_usage",
)

self.admin_router.add_api_route(
"/usage/report/unlock",
self.unlock_report_usage,
methods=["POST"],
operation_id="admin_unlock_report_usage",
)

self.admin_router.add_api_route(
"/usage/flush",
self.flush_usage,
methods=["POST"],
operation_id="admin_flush_usage",
)

async def _reset_organization_flags(self, organization_id: str) -> None:
Expand Down
19 changes: 15 additions & 4 deletions api/ee/src/apis/fastapi/organizations/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ async def require_domains_and_auto_join_disabled(organization_id: str) -> None:


@router.post(
"/domains",
"/domains/",
response_model=OrganizationDomainResponse,
operation_id="create_organization_domain",
)
async def create_domain(
payload: OrganizationDomainCreate,
Expand Down Expand Up @@ -133,6 +134,7 @@ async def create_domain(
@router.post(
"/domains/verify",
response_model=OrganizationDomainResponse,
operation_id="verify_organization_domain",
)
async def verify_domain(
payload: OrganizationDomainVerify,
Expand Down Expand Up @@ -165,8 +167,9 @@ async def verify_domain(


@router.get(
"/domains",
"/domains/",
response_model=List[OrganizationDomainResponse],
operation_id="list_organization_domains",
)
async def list_domains(
request: Request,
Expand All @@ -191,6 +194,7 @@ async def list_domains(
@router.post(
"/domains/{domain_id}/refresh",
response_model=OrganizationDomainResponse,
operation_id="refresh_organization_domain_token",
)
async def refresh_domain_token(
domain_id: str,
Expand Down Expand Up @@ -223,6 +227,7 @@ async def refresh_domain_token(
@router.post(
"/domains/{domain_id}/reset",
response_model=OrganizationDomainResponse,
operation_id="reset_organization_domain",
)
async def reset_domain(
domain_id: str,
Expand Down Expand Up @@ -255,6 +260,7 @@ async def reset_domain(
@router.delete(
"/domains/{domain_id}",
status_code=204,
operation_id="delete_organization_domain",
)
async def delete_domain(
domain_id: str,
Expand Down Expand Up @@ -284,8 +290,9 @@ async def delete_domain(


@router.post(
"/providers",
"/providers/",
response_model=OrganizationProviderResponse,
operation_id="create_organization_provider",
)
async def create_provider(
payload: OrganizationProviderCreate,
Expand Down Expand Up @@ -330,6 +337,7 @@ async def create_provider(
@router.patch(
"/providers/{provider_id}",
response_model=OrganizationProviderResponse,
operation_id="update_organization_provider",
)
async def update_provider(
provider_id: str,
Expand Down Expand Up @@ -367,8 +375,9 @@ async def update_provider(


@router.get(
"/providers",
"/providers/",
response_model=List[OrganizationProviderResponse],
operation_id="list_organization_providers",
)
async def list_providers(
request: Request,
Expand All @@ -393,6 +402,7 @@ async def list_providers(
@router.post(
"/providers/{provider_id}/test",
response_model=OrganizationProviderResponse,
operation_id="test_organization_provider",
)
async def test_provider(
provider_id: str,
Expand Down Expand Up @@ -427,6 +437,7 @@ async def test_provider(
@router.delete(
"/providers/{provider_id}",
status_code=204,
operation_id="delete_organization_provider",
)
async def delete_provider(
provider_id: str,
Expand Down
37 changes: 19 additions & 18 deletions api/ee/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from ee.src.apis.fastapi.organizations.router import (
router as organization_router,
)
from oss.src.apis.fastapi.auth.router import auth_router

# DBS --------------------------------------------------------------------------

Expand Down Expand Up @@ -70,7 +69,8 @@ def extend_main(app: FastAPI):
app.include_router(
router=billing_router.admin_router,
prefix="/admin/billing",
tags=["Admin", "Billing"],
tags=["Admin"],
include_in_schema=False,
)

# ROUTES (more) ------------------------------------------------------------
Expand All @@ -93,14 +93,6 @@ def extend_main(app: FastAPI):
tags=["Workspaces"],
)

# Auth router at root level (no /api prefix) for OAuth callbacks
app.include_router(
auth_router,
prefix="/auth",
tags=["Auth"],
include_in_schema=False,
)

# --------------------------------------------------------------------------

return app
Expand All @@ -112,7 +104,6 @@ def custom_openapi():
EE-aware OpenAPI schema generator, replaces FastAPI's default.

Extends the OSS schema with:
- Billing tag injected before Admin in the sidebar ordering
- APIKeyHeader security scheme and global security requirement
- Server URL pinned from config

Expand All @@ -121,17 +112,27 @@ def custom_openapi():
if app.openapi_schema:
return app.openapi_schema

billing_tag = {
"name": "Billing",
"description": "Subscription management, plans, usage, and Stripe billing (EE only).",
}

oss_tags = list(app.openapi_tags or [])
admin_index = next(
# Insert Access then Billing right before the Admin tag so the final
# order is: ...domain tags..., Access, Billing, Admin, Deprecated.
admin_idx = next(
(i for i, t in enumerate(oss_tags) if t.get("name") == "Admin"),
len(oss_tags),
)
oss_tags.insert(admin_index, billing_tag)
oss_tags.insert(
admin_idx,
{
"name": "Access",
"description": "Authentication discovery, organization access checks, and SSO callback endpoints.",
},
)
oss_tags.insert(
admin_idx + 1,
{
"name": "Billing",
"description": "Subscription, plan, and usage endpoints for workspace billing.",
},
)

schema = get_openapi(
title="Agenta API",
Expand Down
Loading
Loading