# M4 Core API Frontend Guide (Dev)

Status: Active  
Last updated: 2026-02-27  
Audience: Web and mobile frontend developers

## 1) Base URLs (dev)
1. Core API: `https://krow-core-api-e3g6witsvq-uc.a.run.app`

## 2) Auth requirements
1. Send Firebase ID token on protected routes:
```http
Authorization: Bearer <firebase-id-token>
```
2. Health route is public:
- `GET /health`
3. All other routes require Firebase token.

## 3) Standard error envelope
```json
{
  "code": "STRING_CODE",
  "message": "Human readable message",
  "details": {},
  "requestId": "uuid"
}
```

## 4) Core API endpoints

## 4.1 Upload file
1. Route: `POST /core/upload-file`
2. Alias: `POST /uploadFile`
3. Content type: `multipart/form-data`
4. Form fields:
- `file` (required)
- `visibility` (optional: `public` or `private`, default `private`)
- `category` (optional)
5. Accepted file types:
- `application/pdf`
- `image/jpeg`
- `image/jpg`
- `image/png`
- `audio/webm`
- `audio/wav`
- `audio/x-wav`
- `audio/mpeg`
- `audio/mp3`
- `audio/mp4`
- `audio/m4a`
- `audio/aac`
- `audio/ogg`
- `audio/flac`
6. Max upload size: `10 MB` (default)
7. Current behavior: real upload to Cloud Storage (not mock)
8. Success `200` example:
```json
{
  "fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/173...",
  "contentType": "application/pdf",
  "size": 12345,
  "bucket": "krow-workforce-dev-private",
  "path": "uploads/<uid>/173..._file.pdf",
  "requestId": "uuid"
}
```

## 4.2 Create signed URL
1. Route: `POST /core/create-signed-url`
2. Alias: `POST /createSignedUrl`
3. Request body:
```json
{
  "fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/file.pdf",
  "expiresInSeconds": 300
}
```
4. Security checks:
- bucket must be allowed (`krow-workforce-dev-public` or `krow-workforce-dev-private`)
- path must be owned by caller (`uploads/<caller_uid>/...`)
- object must exist
- `expiresInSeconds` must be `<= 900`
5. Success `200` example:
```json
{
  "signedUrl": "https://storage.googleapis.com/...",
  "expiresAt": "2026-02-24T15:22:28.105Z",
  "requestId": "uuid"
}
```
6. Typical errors:
- `400 VALIDATION_ERROR` (bad payload or expiry too high)
- `403 FORBIDDEN` (path not owned by caller)
- `404 NOT_FOUND` (object does not exist)

## 4.3 Invoke model
1. Route: `POST /core/invoke-llm`
2. Alias: `POST /invokeLLM`
3. Request body:
```json
{
  "prompt": "Return JSON with keys summary and risk.",
  "responseJsonSchema": {
    "type": "object",
    "properties": {
      "summary": { "type": "string" },
      "risk": { "type": "string" }
    },
    "required": ["summary", "risk"]
  },
  "fileUrls": []
}
```
4. Current behavior: real Vertex model call (not mock)
- model: `gemini-2.0-flash-001`
- timeout: `20 seconds`
5. Rate limit:
- per-user `20 requests/minute` (default)
- on limit: `429 RATE_LIMITED`
- includes `Retry-After` header
6. Success `200` example:
```json
{
  "result": { "summary": "text", "risk": "Low" },
  "model": "gemini-2.0-flash-001",
  "latencyMs": 367,
  "requestId": "uuid"
}
```

## 4.4 Rapid order transcribe (audio to text)
1. Route: `POST /core/rapid-orders/transcribe`
2. Auth: required
3. Purpose: transcribe uploaded RAPID voice note into text for the RAPID input box.
4. Request body:
```json
{
  "audioFileUri": "gs://krow-workforce-dev-private/uploads/<uid>/rapid-request.webm",
  "locale": "en-US",
  "promptHints": ["server", "urgent"]
}
```
5. Security checks:
- `audioFileUri` must be in allowed bucket
- `audioFileUri` path must be owned by caller (`uploads/<caller_uid>/...`)
- file existence is required in non-mock upload mode
6. Success `200` example:
```json
{
  "transcript": "Need 2 servers ASAP for 4 hours.",
  "confidence": 0.87,
  "language": "en-US",
  "warnings": [],
  "model": "gemini-2.0-flash-001",
  "latencyMs": 412,
  "requestId": "uuid"
}
```
7. Typical errors:
- `400 VALIDATION_ERROR` (invalid payload)
- `401 UNAUTHENTICATED` (missing/invalid bearer token)
- `403 FORBIDDEN` (audio path not owned by caller)
- `429 RATE_LIMITED` (model quota per user)
- `502 MODEL_FAILED` (upstream model output/availability)

## 4.5 Rapid order parse (text to structured draft)
1. Route: `POST /core/rapid-orders/parse`
2. Auth: required
3. Purpose: convert RAPID text into structured one-time order draft JSON for form prefill.
4. Request body:
```json
{
  "text": "Need 2 servers ASAP for 4 hours",
  "locale": "en-US",
  "timezone": "America/New_York",
  "now": "2026-02-27T12:00:00.000Z"
}
```
5. Success `200` example:
```json
{
  "parsed": {
    "orderType": "ONE_TIME",
    "isRapid": true,
    "positions": [
      { "role": "server", "count": 2 }
    ],
    "startAt": "2026-02-27T12:00:00.000Z",
    "endAt": null,
    "durationMinutes": 240,
    "locationHint": null,
    "notes": null,
    "sourceText": "Need 2 servers ASAP for 4 hours"
  },
  "missingFields": [],
  "warnings": [],
  "confidence": {
    "overall": 0.72,
    "fields": {
      "positions": 0.86,
      "startAt": 0.9,
      "durationMinutes": 0.88
    }
  },
  "model": "gemini-2.0-flash-001",
  "latencyMs": 531,
  "requestId": "uuid"
}
```
6. Contract notes:
- unknown request keys are rejected (`400 VALIDATION_ERROR`)
- when information is missing/ambiguous, backend returns `missingFields` and `warnings`
- frontend should use output to prefill one-time order and request user confirmation where needed

## 4.6 Create verification job
1. Route: `POST /core/verifications`
2. Auth: required
3. Purpose: enqueue an async verification job for an uploaded file.
4. Request body:
```json
{
  "type": "attire",
  "subjectType": "worker",
  "subjectId": "<worker-id>",
  "fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/file.pdf",
  "rules": {
    "dressCode": "black shoes"
  }
}
```
5. Success `202` example:
```json
{
  "verificationId": "ver_123",
  "status": "PENDING",
  "type": "attire",
  "requestId": "uuid"
}
```
6. Current machine processing behavior in dev:
- `attire`: live vision check using Vertex Gemini Flash Lite model.
- `government_id`: third-party adapter path (falls back to `NEEDS_REVIEW` if provider is not configured).
- `certification`: third-party adapter path (falls back to `NEEDS_REVIEW` if provider is not configured).

## 4.7 Get verification status
1. Route: `GET /core/verifications/{verificationId}`
2. Auth: required
3. Purpose: polling status from frontend.
4. Success `200` example:
```json
{
  "verificationId": "ver_123",
  "status": "NEEDS_REVIEW",
  "type": "attire",
  "review": null,
  "requestId": "uuid"
}
```

## 4.8 Review verification
1. Route: `POST /core/verifications/{verificationId}/review`
2. Auth: required
3. Purpose: final human decision for the verification.
4. Request body:
```json
{
  "decision": "APPROVED",
  "note": "Manual review passed",
  "reasonCode": "MANUAL_REVIEW"
}
```
5. Success `200` example:
```json
{
  "verificationId": "ver_123",
  "status": "APPROVED",
  "review": {
    "decision": "APPROVED",
    "reviewedBy": "<uid>"
  },
  "requestId": "uuid"
}
```

## 4.9 Retry verification
1. Route: `POST /core/verifications/{verificationId}/retry`
2. Auth: required
3. Purpose: requeue verification to run again.
4. Success `202` example: status resets to `PENDING`.

## 5) Frontend fetch examples (web)

## 5.1 Signed URL request
```ts
const token = await firebaseAuth.currentUser?.getIdToken();
const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/create-signed-url', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    fileUri: 'gs://krow-workforce-dev-private/uploads/<uid>/file.pdf',
    expiresInSeconds: 300,
  }),
});
const data = await res.json();
```

## 5.2 Model request
```ts
const token = await firebaseAuth.currentUser?.getIdToken();
const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/invoke-llm', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    prompt: 'Return JSON with status.',
    responseJsonSchema: {
      type: 'object',
      properties: { status: { type: 'string' } },
      required: ['status'],
    },
  }),
});
const data = await res.json();
```

## 5.3 Rapid audio transcribe request
```ts
const token = await firebaseAuth.currentUser?.getIdToken();
const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/rapid-orders/transcribe', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    audioFileUri: 'gs://krow-workforce-dev-private/uploads/<uid>/rapid-request.webm',
    locale: 'en-US',
    promptHints: ['server', 'urgent'],
  }),
});
const data = await res.json();
```

## 5.4 Rapid text parse request
```ts
const token = await firebaseAuth.currentUser?.getIdToken();
const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/rapid-orders/parse', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    text: 'Need 2 servers ASAP for 4 hours',
    locale: 'en-US',
    timezone: 'America/New_York',
  }),
});
const data = await res.json();
```

## 6) Notes for frontend team
1. Use canonical `/core/*` routes for new work.
2. Aliases exist only for migration compatibility.
3. `requestId` in responses should be logged client-side for debugging.
4. For 429 on model route, retry with exponential backoff and respect `Retry-After`.
5. Verification routes are now available in dev under `/core/verifications*`.
6. Current verification processing is async and returns machine statuses first (`PENDING`, `PROCESSING`, `NEEDS_REVIEW`, etc.).
7. Full verification design and policy details:
   `docs/MILESTONES/M4/planning/m4-verification-architecture-contract.md`.
