docs(m4): scope api docs to core endpoints only
This commit is contained in:
@@ -14,3 +14,4 @@
|
|||||||
| 2026-02-24 | 0.1.9 | Switched core API from mock behavior to real GCS upload/signed URLs and real Vertex model calls in dev deployment. |
|
| 2026-02-24 | 0.1.9 | Switched core API from mock behavior to real GCS upload/signed URLs and real Vertex model calls in dev deployment. |
|
||||||
| 2026-02-24 | 0.1.10 | Hardened core APIs with signed URL ownership/expiry checks, object existence checks, and per-user LLM rate limiting. |
|
| 2026-02-24 | 0.1.10 | Hardened core APIs with signed URL ownership/expiry checks, object existence checks, and per-user LLM rate limiting. |
|
||||||
| 2026-02-24 | 0.1.11 | Added frontend-ready core API guide and linked M4 API catalog to it as source of truth for consumption. |
|
| 2026-02-24 | 0.1.11 | Added frontend-ready core API guide and linked M4 API catalog to it as source of truth for consumption. |
|
||||||
|
| 2026-02-24 | 0.1.12 | Reduced M4 API docs to core-only scope and removed command-route references until command implementation is complete. |
|
||||||
|
|||||||
@@ -1,30 +1,26 @@
|
|||||||
# M4 API Catalog (Implementation Contract)
|
# M4 API Catalog (Core Only)
|
||||||
|
|
||||||
Status: Active (Planning + Route Inventory)
|
Status: Active
|
||||||
Date: 2026-02-24
|
Date: 2026-02-24
|
||||||
Owner: Technical Lead
|
Owner: Technical Lead
|
||||||
Environment: dev
|
Environment: dev
|
||||||
|
|
||||||
## Frontend source of truth
|
## Frontend source of truth
|
||||||
For frontend implementation, use:
|
Use this file and `docs/MILESTONES/M4/planning/m4-core-api-frontend-guide.md` for core endpoint consumption.
|
||||||
- `docs/MILESTONES/M4/planning/m4-core-api-frontend-guide.md`
|
|
||||||
|
|
||||||
Reason:
|
|
||||||
- this catalog is the broader M4 planning contract
|
|
||||||
- the frontend guide is the exact deployed request/response contract
|
|
||||||
|
|
||||||
## 1) Scope and purpose
|
## 1) Scope and purpose
|
||||||
This file defines the backend endpoint contract for the M4 foundation build.
|
This catalog defines the currently implemented core backend contract for M4.
|
||||||
|
|
||||||
## 2) Global API rules
|
## 2) Global API rules
|
||||||
1. Canonical route groups:
|
1. Route group in scope: `/core/*`.
|
||||||
- `/core/*` for foundational integration routes
|
2. Compatibility aliases in scope:
|
||||||
- `/commands/*` for business-critical writes
|
- `POST /uploadFile` -> `POST /core/upload-file`
|
||||||
2. Foundation phase security model:
|
- `POST /createSignedUrl` -> `POST /core/create-signed-url`
|
||||||
- authenticated user required
|
- `POST /invokeLLM` -> `POST /core/invoke-llm`
|
||||||
- role map enforcement deferred
|
3. Auth model:
|
||||||
- policy hook required in handler design
|
- `GET /health` is public in dev
|
||||||
3. Standard error envelope:
|
- all other routes require `Authorization: Bearer <firebase-id-token>`
|
||||||
|
4. Standard error envelope:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"code": "STRING_CODE",
|
"code": "STRING_CODE",
|
||||||
@@ -33,100 +29,71 @@ This file defines the backend endpoint contract for the M4 foundation build.
|
|||||||
"requestId": "optional-request-id"
|
"requestId": "optional-request-id"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
4. Required request headers:
|
5. Response header:
|
||||||
- `Authorization: Bearer <firebase-token>`
|
|
||||||
- `X-Request-Id: <uuid>` (optional but recommended)
|
|
||||||
5. Required response headers:
|
|
||||||
- `X-Request-Id`
|
- `X-Request-Id`
|
||||||
6. Validation:
|
|
||||||
- all input validated server-side
|
|
||||||
- reject unknown/invalid fields
|
|
||||||
7. Logging:
|
|
||||||
- route
|
|
||||||
- requestId
|
|
||||||
- actorId
|
|
||||||
- latencyMs
|
|
||||||
- outcome
|
|
||||||
8. Timeouts and retries:
|
|
||||||
- command writes must be retry-safe
|
|
||||||
- use idempotency keys for command write routes
|
|
||||||
9. Idempotency storage:
|
|
||||||
- store in Cloud SQL table
|
|
||||||
- key scope: `userId + route + idempotencyKey`
|
|
||||||
- key retention: 24 hours
|
|
||||||
- repeated key returns original response payload
|
|
||||||
|
|
||||||
## 3) Compatibility aliases (transition)
|
## 3) Core routes
|
||||||
1. `POST /uploadFile` -> `POST /core/upload-file`
|
|
||||||
2. `POST /createSignedUrl` -> `POST /core/create-signed-url`
|
|
||||||
3. `POST /invokeLLM` -> `POST /core/invoke-llm`
|
|
||||||
|
|
||||||
## 4) Rate-limit baseline (initial)
|
## 3.1 Upload file
|
||||||
1. `/core/invoke-llm`: 60 requests per minute per user
|
|
||||||
2. `/core/upload-file`: 30 requests per minute per user
|
|
||||||
3. `/core/create-signed-url`: 120 requests per minute per user
|
|
||||||
4. `/commands/*`: 60 requests per minute per user
|
|
||||||
|
|
||||||
## 4.1 Timeout baseline (initial)
|
|
||||||
1. `/core/invoke-llm`: 20-second hard timeout
|
|
||||||
2. other `/core/*` routes: 10-second timeout
|
|
||||||
3. `/commands/*` routes: 15-second timeout
|
|
||||||
|
|
||||||
## 5) Core routes
|
|
||||||
|
|
||||||
## 5.1 Upload file
|
|
||||||
1. Method and route: `POST /core/upload-file`
|
1. Method and route: `POST /core/upload-file`
|
||||||
2. Auth: required
|
2. Request format: `multipart/form-data`
|
||||||
3. Idempotency key: optional
|
3. Fields:
|
||||||
4. Request: multipart form data
|
|
||||||
- `file` (required)
|
- `file` (required)
|
||||||
|
- `visibility` (`public` or `private`, optional)
|
||||||
- `category` (optional)
|
- `category` (optional)
|
||||||
- `visibility` (optional: `public` or `private`)
|
4. Accepted types:
|
||||||
5. Success `200`:
|
- `application/pdf`
|
||||||
|
- `image/jpeg`
|
||||||
|
- `image/jpg`
|
||||||
|
- `image/png`
|
||||||
|
5. Max size: `10 MB` (default)
|
||||||
|
6. Behavior: real upload to Cloud Storage.
|
||||||
|
7. Success `200`:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"fileUri": "gs://bucket/path/file.ext",
|
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/...",
|
||||||
"contentType": "application/pdf",
|
"contentType": "application/pdf",
|
||||||
"size": 12345,
|
"size": 12345,
|
||||||
"bucket": "krow-uploads-private",
|
"bucket": "krow-workforce-dev-private",
|
||||||
"path": "documents/staff/..."
|
"path": "uploads/<uid>/...",
|
||||||
|
"requestId": "uuid"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
6. Errors:
|
8. Errors:
|
||||||
- `UNAUTHENTICATED`
|
- `UNAUTHENTICATED`
|
||||||
- `INVALID_FILE_TYPE`
|
- `INVALID_FILE_TYPE`
|
||||||
- `FILE_TOO_LARGE`
|
- `FILE_TOO_LARGE`
|
||||||
- `UPLOAD_FAILED`
|
|
||||||
|
|
||||||
## 5.2 Create signed URL
|
## 3.2 Create signed URL
|
||||||
1. Method and route: `POST /core/create-signed-url`
|
1. Method and route: `POST /core/create-signed-url`
|
||||||
2. Auth: required
|
2. Request:
|
||||||
3. Idempotency key: optional
|
|
||||||
4. Request:
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"fileUri": "gs://bucket/path/file.ext",
|
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/file.pdf",
|
||||||
"expiresInSeconds": 300
|
"expiresInSeconds": 300
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
5. Success `200`:
|
3. Security checks:
|
||||||
|
- bucket must be allowed
|
||||||
|
- path must be owned by caller (`uploads/<caller_uid>/...`)
|
||||||
|
- object must exist
|
||||||
|
- `expiresInSeconds <= 900`
|
||||||
|
4. Success `200`:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"signedUrl": "https://...",
|
"signedUrl": "https://storage.googleapis.com/...",
|
||||||
"expiresAt": "2026-02-24T15:00:00Z"
|
"expiresAt": "2026-02-24T15:22:28.105Z",
|
||||||
|
"requestId": "uuid"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
6. Errors:
|
5. Errors:
|
||||||
- `UNAUTHENTICATED`
|
- `VALIDATION_ERROR`
|
||||||
- `FORBIDDEN_FILE_ACCESS`
|
- `FORBIDDEN`
|
||||||
- `INVALID_EXPIRES_IN`
|
- `NOT_FOUND`
|
||||||
- `SIGN_URL_FAILED`
|
|
||||||
|
|
||||||
## 5.3 Invoke model
|
## 3.3 Invoke model
|
||||||
1. Method and route: `POST /core/invoke-llm`
|
1. Method and route: `POST /core/invoke-llm`
|
||||||
2. Auth: required
|
2. Request:
|
||||||
3. Idempotency key: optional
|
|
||||||
4. Request:
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"prompt": "...",
|
"prompt": "...",
|
||||||
@@ -134,103 +101,48 @@ This file defines the backend endpoint contract for the M4 foundation build.
|
|||||||
"fileUrls": []
|
"fileUrls": []
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
3. Behavior:
|
||||||
|
- real Vertex AI call
|
||||||
|
- model default: `gemini-2.0-flash-001`
|
||||||
|
- timeout default: `20 seconds`
|
||||||
|
4. Rate limit:
|
||||||
|
- `20 requests/minute` per user (default)
|
||||||
|
- when exceeded: `429 RATE_LIMITED` and `Retry-After` header
|
||||||
5. Success `200`:
|
5. Success `200`:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"result": {},
|
"result": {},
|
||||||
"model": "provider/model-name",
|
"model": "gemini-2.0-flash-001",
|
||||||
"latencyMs": 980
|
"latencyMs": 367,
|
||||||
|
"requestId": "uuid"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
6. Errors:
|
6. Errors:
|
||||||
- `UNAUTHENTICATED`
|
- `UNAUTHENTICATED`
|
||||||
- `INVALID_SCHEMA`
|
- `VALIDATION_ERROR`
|
||||||
- `MODEL_TIMEOUT`
|
- `MODEL_TIMEOUT`
|
||||||
- `MODEL_FAILED`
|
- `MODEL_FAILED`
|
||||||
7. Provider default:
|
- `RATE_LIMITED`
|
||||||
- Vertex AI Gemini
|
|
||||||
|
|
||||||
## 5.4 Health check
|
## 3.4 Health
|
||||||
1. Method and route: `GET /healthz`
|
1. Method and route: `GET /health`
|
||||||
2. Auth: optional (internal policy)
|
2. Success `200`:
|
||||||
3. Success `200`:
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"service": "krow-backend",
|
"service": "krow-core-api",
|
||||||
"version": "commit-or-tag"
|
"version": "dev",
|
||||||
|
"requestId": "uuid"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5.5 Storage bucket policy defaults (dev)
|
## 4) Locked defaults
|
||||||
1. Public bucket: `krow-workforce-dev-public`
|
1. Validation library: `zod`.
|
||||||
2. Private bucket: `krow-workforce-dev-private`
|
2. Validation schema location: `backend/core-api/src/contracts/`.
|
||||||
3. Private objects are never returned directly; only signed URLs are returned.
|
3. Buckets:
|
||||||
|
|
||||||
## 6) Command routes (wave 1)
|
|
||||||
|
|
||||||
## 6.1 Create order
|
|
||||||
1. Method and route: `POST /commands/orders/create`
|
|
||||||
2. Auth: required
|
|
||||||
3. Idempotency key: required
|
|
||||||
4. Purpose: create order + shifts + roles atomically
|
|
||||||
5. Replaces:
|
|
||||||
- `apps/web/src/features/operations/orders/components/CreateOrderDialog.tsx`
|
|
||||||
- `apps/mobile/packages/features/client/home/lib/src/presentation/widgets/shift_order_form_sheet.dart`
|
|
||||||
- `apps/mobile/packages/features/client/create_order/lib/src/data/repositories_impl/client_create_order_repository_impl.dart`
|
|
||||||
|
|
||||||
## 6.2 Update order
|
|
||||||
1. Method and route: `POST /commands/orders/{orderId}/update`
|
|
||||||
2. Auth: required
|
|
||||||
3. Idempotency key: required
|
|
||||||
4. Purpose: policy-safe multi-entity order update
|
|
||||||
5. Replaces:
|
|
||||||
- `apps/web/src/features/operations/orders/EditOrder.tsx`
|
|
||||||
- `apps/mobile/packages/features/client/view_orders/lib/src/presentation/widgets/view_order_card.dart`
|
|
||||||
|
|
||||||
## 6.3 Cancel order
|
|
||||||
1. Method and route: `POST /commands/orders/{orderId}/cancel`
|
|
||||||
2. Auth: required
|
|
||||||
3. Idempotency key: required
|
|
||||||
4. Purpose: enforce cancellation policy and return explicit conflict code
|
|
||||||
5. Replaces:
|
|
||||||
- `apps/web/src/features/operations/orders/OrderDetail.tsx`
|
|
||||||
|
|
||||||
## 6.4 Change shift status
|
|
||||||
1. Method and route: `POST /commands/shifts/{shiftId}/change-status`
|
|
||||||
2. Auth: required
|
|
||||||
3. Idempotency key: required
|
|
||||||
4. Purpose: enforce state transitions server-side
|
|
||||||
5. Replaces:
|
|
||||||
- `apps/web/src/features/operations/tasks/TaskBoard.tsx`
|
|
||||||
|
|
||||||
## 6.5 Assign staff
|
|
||||||
1. Method and route: `POST /commands/shifts/{shiftId}/assign-staff`
|
|
||||||
2. Auth: required
|
|
||||||
3. Idempotency key: required
|
|
||||||
4. Purpose: assign + count update + conflict checks atomically
|
|
||||||
5. Replaces:
|
|
||||||
- `apps/web/src/features/operations/orders/components/AssignStaffModal.tsx`
|
|
||||||
|
|
||||||
## 6.6 Accept shift
|
|
||||||
1. Method and route: `POST /commands/shifts/{shiftId}/accept`
|
|
||||||
2. Auth: required
|
|
||||||
3. Idempotency key: required
|
|
||||||
4. Purpose: application + counters + rollback-safe behavior in one command
|
|
||||||
5. Replaces:
|
|
||||||
- `apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart`
|
|
||||||
|
|
||||||
## 7) Locked defaults before coding starts
|
|
||||||
1. Idempotency keys are stored in Cloud SQL with 24-hour retention.
|
|
||||||
2. Request validation library is `zod`.
|
|
||||||
3. Validation schema location is `backend/<service>/src/contracts/`.
|
|
||||||
4. Storage buckets are:
|
|
||||||
- `krow-workforce-dev-public`
|
- `krow-workforce-dev-public`
|
||||||
- `krow-workforce-dev-private`
|
- `krow-workforce-dev-private`
|
||||||
5. Model provider is Vertex AI Gemini with a 20-second timeout for `/core/invoke-llm`.
|
4. Model provider: Vertex AI Gemini.
|
||||||
|
5. Max signed URL expiry: `900` seconds.
|
||||||
## 8) Target response-time objectives (p95)
|
6. LLM timeout: `20000` ms.
|
||||||
1. `/healthz` under 200ms
|
7. LLM rate limit: `20` requests/minute/user.
|
||||||
2. `/core/create-signed-url` under 500ms
|
|
||||||
3. `/commands/*` under 1500ms
|
|
||||||
4. `/core/invoke-llm` under 15000ms
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ Audience: Web and mobile frontend developers
|
|||||||
|
|
||||||
## 1) Base URLs (dev)
|
## 1) Base URLs (dev)
|
||||||
1. Core API: `https://krow-core-api-e3g6witsvq-uc.a.run.app`
|
1. Core API: `https://krow-core-api-e3g6witsvq-uc.a.run.app`
|
||||||
2. Command API: `https://krow-command-api-e3g6witsvq-uc.a.run.app`
|
|
||||||
|
|
||||||
## 2) Auth requirements
|
## 2) Auth requirements
|
||||||
1. Send Firebase ID token on protected routes:
|
1. Send Firebase ID token on protected routes:
|
||||||
@@ -119,31 +118,9 @@ Authorization: Bearer <firebase-id-token>
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5) Command API endpoint currently ready for consumption
|
## 5) Frontend fetch examples (web)
|
||||||
|
|
||||||
## 5.1 Create order command (scaffold)
|
## 5.1 Signed URL request
|
||||||
1. Route: `POST /commands/orders/create`
|
|
||||||
2. Required headers:
|
|
||||||
- `Authorization: Bearer <firebase-id-token>`
|
|
||||||
- `Idempotency-Key: <unique-client-key>`
|
|
||||||
3. Current behavior:
|
|
||||||
- validates auth + idempotency
|
|
||||||
- returns accepted scaffold response
|
|
||||||
- duplicate key returns the original response payload
|
|
||||||
4. Success `200` example:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"accepted": true,
|
|
||||||
"route": "/commands/orders/create",
|
|
||||||
"commandId": "/commands/orders/create:173...",
|
|
||||||
"idempotencyKey": "client-key-123",
|
|
||||||
"requestId": "uuid"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6) Frontend fetch examples (web)
|
|
||||||
|
|
||||||
## 6.1 Signed URL request
|
|
||||||
```ts
|
```ts
|
||||||
const token = await firebaseAuth.currentUser?.getIdToken();
|
const token = await firebaseAuth.currentUser?.getIdToken();
|
||||||
const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/create-signed-url', {
|
const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/create-signed-url', {
|
||||||
@@ -160,7 +137,7 @@ const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/crea
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
```
|
```
|
||||||
|
|
||||||
## 6.2 Model request
|
## 5.2 Model request
|
||||||
```ts
|
```ts
|
||||||
const token = await firebaseAuth.currentUser?.getIdToken();
|
const token = await firebaseAuth.currentUser?.getIdToken();
|
||||||
const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/invoke-llm', {
|
const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/invoke-llm', {
|
||||||
@@ -181,7 +158,7 @@ const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/invo
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
```
|
```
|
||||||
|
|
||||||
## 7) Notes for frontend team
|
## 6) Notes for frontend team
|
||||||
1. Use canonical `/core/*` routes for new work.
|
1. Use canonical `/core/*` routes for new work.
|
||||||
2. Aliases exist only for migration compatibility.
|
2. Aliases exist only for migration compatibility.
|
||||||
3. `requestId` in responses should be logged client-side for debugging.
|
3. `requestId` in responses should be logged client-side for debugging.
|
||||||
|
|||||||
Reference in New Issue
Block a user